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 */
15import { FilterByAnalysis, NativeMemoryExpression } from '../../bean/NativeHook';
16import {
17  convertJSON,
18  DataCache,
19  getByteWithUnit,
20  HeapTreeDataBean,
21  LogicHandler,
22  MerageBean,
23  merageBeanDataSplit,
24  postMessage,
25  setFileName,
26} from './ProcedureLogicWorkerCommon';
27
28type CallInfoMap = {
29  [key: string]: NativeHookCallInfo;
30};
31
32type StatisticMap = {
33  [key: string]: NativeHookStatistics;
34};
35
36const HAP_TYPE = ['.hap', '.har', '.hsp'];
37
38export class ProcedureLogicWorkerNativeMemory extends LogicHandler {
39  selectTotalSize = 0;
40  selectTotalCount = 0;
41  currentTreeMapData: CallInfoMap = {};
42  currentTreeList: NativeHookCallInfo[] = [];
43  queryAllCallchainsSamples: NativeHookStatistics[] = [];
44  currentSamples: NativeHookStatistics[] = [];
45  allThreads: NativeHookCallInfo[] = [];
46  splitMapData: CallInfoMap = {};
47  searchValue: string = '';
48  currentEventId: string = '';
49  realTimeDif: number = 0;
50  responseTypes: { key: number; value: string }[] = [];
51  totalNS: number = 0;
52  isStatisticMode: boolean = false;
53  boxRangeNativeHook: Array<NativeMemory> = [];
54  clearBoxSelectionData: boolean = false;
55  nmArgs?: Map<string, unknown>;
56  private dataCache = DataCache.getInstance();
57  isHideThread: boolean = false;
58  private currentSelectIPid: number = 1;
59  useFreedSize: boolean = false;
60
61  handle(data: unknown): void {
62    //@ts-ignore
63    this.currentEventId = data.id;
64    //@ts-ignore
65    if (data && data.type) {
66      //@ts-ignore
67      switch (data.type) {
68        case 'native-memory-init':
69          //@ts-ignore
70          this.nmInit(data.params);
71          break;
72        case 'native-memory-queryNMFrameData':
73          this.nmQueryNMFrameData(data);
74          break;
75        case 'native-memory-queryCallchainsSamples':
76          this.nmQueryCallchainsSamples(data);
77          break;
78        case 'native-memory-queryStatisticCallchainsSamples':
79          this.nmQueryStatisticCallchainsSamples(data);
80          break;
81        case 'native-memory-queryAnalysis':
82          this.nmQueryAnalysis(data);
83          break;
84        case 'native-memory-queryNativeHookEvent':
85          this.nmQueryNativeHookEvent(data);
86          break;
87        case 'native-memory-action':
88          this.nmAction(data);
89          break;
90        case 'native-memory-calltree-action':
91          this.nmCalltreeAction(data);
92          break;
93        case 'native-memory-init-responseType':
94          this.nmInitResponseType(data);
95          break;
96        case 'native-memory-get-responseType':
97          this.nmGetResponseType(data);
98          break;
99        case 'native-memory-reset':
100          this.isHideThread = false;
101          break;
102        case 'native-memory-set-current_ipid':
103          //@ts-ignore
104          this.currentSelectIPid = data.params;
105      }
106    }
107  }
108  private nmInit(params: unknown): void {
109    this.clearAll();
110    //@ts-ignore
111    if (params.isRealtime) {
112      //@ts-ignore
113      this.realTimeDif = params.realTimeDif;
114    }
115    this.initNMFrameData();
116  }
117  private nmQueryNMFrameData(data: unknown): void {
118    //@ts-ignore
119    let arr = convertJSON(data.params.list) || [];
120    //@ts-ignore
121    this.initNMStack(arr);
122    arr = [];
123    self.postMessage({
124      //@ts-ignore
125      id: data.id,
126      action: 'native-memory-init',
127      results: [],
128    });
129  }
130  private nmQueryCallchainsSamples(data: unknown): void {
131    this.searchValue = '';
132    //@ts-ignore
133    if (data.params.list) {
134      //@ts-ignore
135      let callchainsSamples = convertJSON(data.params.list) || [];
136      //@ts-ignore
137      this.queryAllCallchainsSamples = callchainsSamples;
138      this.freshCurrentCallchains(this.queryAllCallchainsSamples, true);
139      // @ts-ignore
140      self.postMessage({
141        //@ts-ignore
142        id: data.id,
143        //@ts-ignore
144        action: data.action,
145        results: this.allThreads,
146      });
147    } else {
148      this.queryCallchainsSamples(
149        'native-memory-queryCallchainsSamples',
150        //@ts-ignore
151        data.params.leftNs,
152        //@ts-ignore
153        data.params.rightNs,
154        //@ts-ignore
155        data.params.types
156      );
157    }
158  }
159  private nmQueryStatisticCallchainsSamples(data: unknown): void {
160    this.searchValue = '';
161    //@ts-ignore
162    if (data.params.list) {
163      //@ts-ignore
164      let samples = convertJSON(data.params.list) || [];
165      //@ts-ignore
166      this.queryAllCallchainsSamples = samples;
167      this.freshCurrentCallchains(this.queryAllCallchainsSamples, true);
168      // @ts-ignore
169      self.postMessage({
170        //@ts-ignore
171        id: data.id,
172        //@ts-ignore
173        action: data.action,
174        results: this.allThreads,
175      });
176    } else {
177      this.queryStatisticCallchainsSamples(
178        'native-memory-queryStatisticCallchainsSamples',
179        //@ts-ignore
180        data.params.leftNs,
181        //@ts-ignore
182        data.params.rightNs,
183        //@ts-ignore
184        data.params.types
185      );
186    }
187  }
188  private nmQueryAnalysis(data: unknown): void {
189    //@ts-ignore
190    if (data.params.list) {
191      //@ts-ignore
192      let samples = convertJSON(data.params.list) || [];
193      //@ts-ignore
194      this.queryAllCallchainsSamples = samples;
195      self.postMessage({
196        //@ts-ignore
197        id: data.id,
198        //@ts-ignore
199        action: data.action,
200        results: this.combineStatisticAndCallChain(this.queryAllCallchainsSamples),
201      });
202    } else {
203      //@ts-ignore
204      if (data.params.isStatistic) {
205        this.isStatisticMode = true;
206        this.queryStatisticCallchainsSamples(
207          'native-memory-queryAnalysis',
208          //@ts-ignore
209          data.params.leftNs,
210          //@ts-ignore
211          data.params.rightNs,
212          //@ts-ignore
213          data.params.types
214        );
215      } else {
216        this.isStatisticMode = false;
217        this.queryCallchainsSamples(
218          'native-memory-queryAnalysis',
219          //@ts-ignore
220          data.params.leftNs,
221          //@ts-ignore
222          data.params.rightNs,
223          //@ts-ignore
224          data.params.types
225        );
226      }
227    }
228  }
229  private nmQueryNativeHookEvent(data: unknown): void {
230    //@ts-ignore
231    const params = data.params;
232    if (params) {
233      if (params.list) {
234        //@ts-ignore
235        this.boxRangeNativeHook = convertJSON(params.list);
236        if (this.nmArgs?.get('refresh')) {
237          this.clearBoxSelectionData = this.boxRangeNativeHook.length > 100_0000;
238        }
239        this.supplementNativeHoodData();
240        //@ts-ignore
241        postMessage(data.id, data.action, this.resolvingActionNativeMemory(this.nmArgs!), 50_0000);
242        if (this.clearBoxSelectionData) {
243          this.boxRangeNativeHook = [];
244        }
245      } else if (params.get('refresh') || this.boxRangeNativeHook.length === 0) {
246        this.nmArgs = params;
247        let leftNs = params.get('leftNs');
248        let rightNs = params.get('rightNs');
249        let types = params.get('types');
250        this.boxRangeNativeHook = [];
251        this.queryNativeHookEvent(leftNs, rightNs, types);
252      } else {
253        this.nmArgs = params;
254        //@ts-ignore
255        postMessage(data.id, data.action, this.resolvingActionNativeMemory(this.nmArgs!), 50_0000);
256        if (this.clearBoxSelectionData) {
257          this.boxRangeNativeHook = [];
258        }
259      }
260    }
261  }
262  private nmAction(data: { params?: unknown; id?: string; action?: string }): void {
263    if (data.params) {
264      self.postMessage({
265        id: data.id,
266        action: data.action,
267        //@ts-ignore
268        results: this.resolvingAction(data.params),
269      });
270    }
271  }
272  private nmCalltreeAction(data: { params?: unknown; id?: string; action?: string }): void {
273    if (data.params) {
274      self.postMessage({
275        id: data.id,
276        action: data.action,
277        //@ts-ignore
278        results: this.resolvingNMCallAction(data.params),
279      });
280    }
281  }
282  private nmInitResponseType(data: { params?: unknown; id?: string; action?: string }): void {
283    //@ts-ignore
284    this.initResponseTypeList(data.params);
285    self.postMessage({
286      id: data.id,
287      action: data.action,
288      results: [],
289    });
290  }
291  private nmGetResponseType(data: { params?: unknown; id?: string; action?: string }): void {
292    self.postMessage({
293      id: data.id,
294      action: data.action,
295      results: this.responseTypes,
296    });
297  }
298  queryNativeHookEvent(leftNs: number, rightNs: number, types: Array<string>): void {
299    let condition =
300      types.length === 1
301        ? `and A.event_type = ${types[0]}`
302        : "and (A.event_type = 'AllocEvent' or A.event_type = 'MmapEvent')";
303    let libId = this.nmArgs?.get('filterResponseType');
304    let allocType = this.nmArgs?.get('filterAllocType');
305    let eventType = this.nmArgs?.get('filterEventType');
306    if (libId !== undefined && libId !== -1) {
307      condition = `${condition} and last_lib_id = ${libId}`; // filter lib
308    }
309    if (eventType === '1') {
310      condition = `${condition} and event_type = 'AllocEvent'`;
311    }
312    if (eventType === '2') {
313      condition = `${condition} and event_type = 'MmapEvent'`;
314    }
315    if (allocType === '1') {
316      condition = `${condition} and ((A.end_ts - B.start_ts) > ${rightNs} or A.end_ts is null)`;
317    }
318    if (allocType === '2') {
319      condition = `${condition} and (A.end_ts - B.start_ts) <= ${rightNs}`;
320    }
321    let sql = `
322    select
323      callchain_id as eventId,
324      event_type as eventType,
325      heap_size as heapSize,
326      ('0x' || addr) as addr,
327      (A.start_ts - B.start_ts) as startTs,
328      (A.end_ts - B.start_ts) as endTs,
329      tid as threadId,
330      sub_type_id as subTypeId,
331      ifnull(last_lib_id,0) as lastLibId,
332      ifnull(last_symbol_id,0) as lastSymbolId
333    from
334      native_hook A,
335      trace_range B
336    left join
337      thread t
338    on
339      A.itid = t.id
340    where
341    A.start_ts - B.start_ts between ${leftNs} and ${rightNs} ${condition}
342    and A.ipid = ${this.currentSelectIPid}
343    `;
344    this.queryData(this.currentEventId, 'native-memory-queryNativeHookEvent', sql, {});
345  }
346
347  supplementNativeHoodData(): void {
348    let len = this.boxRangeNativeHook.length;
349    for (let i = 0, j = len - 1; i <= j; i++, j--) {
350      this.fillNativeHook(this.boxRangeNativeHook[i], i);
351      if (i !== j) {
352        this.fillNativeHook(this.boxRangeNativeHook[j], j);
353      }
354    }
355  }
356
357  fillNativeHook(memory: NativeMemory, index: number): void {
358    if (memory.subTypeId !== null && memory.subType === undefined) {
359      memory.subType = this.dataCache.dataDict.get(memory.subTypeId) || '-';
360    }
361    memory.index = index;
362    let arr = this.dataCache.nmHeapFrameMap.get(memory.eventId) || [];
363    let frame = Array.from(arr)
364      .reverse()
365      .find((item: HeapTreeDataBean): boolean => {
366        let fileName = this.dataCache.dataDict.get(item.fileId);
367        return !((fileName ?? '').includes('libc++') || (fileName ?? '').includes('musl'));
368      });
369    if (frame === null || frame === undefined) {
370      if (arr.length > 0) {
371        frame = arr[0];
372      }
373    }
374    if (frame !== null && frame !== undefined) {
375      memory.symbol = this.groupCutFilePath(frame.symbolId, this.dataCache.dataDict.get(frame.symbolId) || '');
376      memory.library = this.groupCutFilePath(frame.fileId, this.dataCache.dataDict.get(frame.fileId) || 'Unknown Path');
377    } else {
378      memory.symbol = '-';
379      memory.library = '-';
380    }
381  }
382
383  initResponseTypeList(list: unknown[]): void {
384    this.responseTypes = [
385      {
386        key: -1,
387        value: 'ALL',
388      },
389    ];
390    list.forEach((item: unknown): void => {
391      //@ts-ignore
392      if (item.lastLibId === null) {
393        this.responseTypes.push({
394          key: 0,
395          value: '-',
396        });
397      } else {
398        this.responseTypes.push({
399          //@ts-ignore
400          key: item.lastLibId,
401          //@ts-ignore
402          value: this.groupCutFilePath(item.lastLibId, item.value) || '-',
403        });
404      }
405    });
406  }
407  initNMFrameData(): void {
408    this.queryData(
409      this.currentEventId,
410      'native-memory-queryNMFrameData',
411      `select h.symbol_id as symbolId, h.file_id as fileId, h.depth, h.callchain_id as eventId, h.vaddr as addr
412                    from native_hook_frame h
413        `,
414      {}
415    );
416  }
417  initNMStack(frameArr: Array<HeapTreeDataBean>): void {
418    frameArr.map((frame): void => {
419      let frameEventId = frame.eventId;
420      if (this.dataCache.nmHeapFrameMap.has(frameEventId)) {
421        this.dataCache.nmHeapFrameMap.get(frameEventId)!.push(frame);
422      } else {
423        this.dataCache.nmHeapFrameMap.set(frameEventId, [frame]);
424      }
425    });
426  }
427  resolvingAction(paramMap: Map<string, unknown>): Array<NativeHookCallInfo | NativeMemory | HeapStruct> {
428    let actionType = paramMap.get('actionType');
429    if (actionType === 'memory-stack') {
430      return this.resolvingActionNativeMemoryStack(paramMap);
431    } else if (actionType === 'native-memory-state-change') {
432      let startTs = paramMap.get('startTs');
433      let currentSelection = this.boxRangeNativeHook.filter((item) => {
434        return item.startTs === startTs;
435      });
436      if (currentSelection.length > 0) {
437        currentSelection[0].isSelected = true;
438      }
439      return [];
440    } else {
441      return [];
442    }
443  }
444
445  resolvingActionNativeMemoryStack(paramMap: Map<string, unknown>): NativeHookCallInfo[] {
446    let eventId = paramMap.get('eventId');
447    //@ts-ignore
448    let frameArr = this.dataCache.nmHeapFrameMap.get(eventId) || [];
449    let arr: Array<NativeHookCallInfo> = [];
450    frameArr.map((frame: HeapTreeDataBean): void => {
451      let target = new NativeHookCallInfo();
452      target.eventId = frame.eventId;
453      target.depth = frame.depth;
454      target.addr = frame.addr;
455      target.symbol = this.groupCutFilePath(frame.symbolId, this.dataCache.dataDict.get(frame.symbolId) || '') ?? '';
456      target.lib = this.groupCutFilePath(frame.fileId, this.dataCache.dataDict.get(frame.fileId) || '') ?? '';
457      target.type = target.lib.endsWith('.so.1') || target.lib.endsWith('.dll') || target.lib.endsWith('.so') ? 0 : 1;
458      arr.push(target);
459    });
460    return arr;
461  }
462
463  resolvingActionNativeMemory(paramMap: Map<string, unknown>): Array<NativeMemory> {
464    let filterAllocType = paramMap.get('filterAllocType');
465    let filterEventType = paramMap.get('filterEventType');
466    let filterResponseType = paramMap.get('filterResponseType');
467    let leftNs = paramMap.get('leftNs');
468    let rightNs = paramMap.get('rightNs');
469    let sortColumn = paramMap.get('sortColumn');
470    let sortType = paramMap.get('sortType');
471    let statisticsSelection = paramMap.get('statisticsSelection');
472    let filter = this.boxRangeNativeHook;
473    if (
474      (filterAllocType !== undefined && filterAllocType !== 0) ||
475      (filterEventType !== undefined && filterEventType !== 0) ||
476      (filterResponseType !== undefined && filterResponseType !== -1)
477    ) {
478      filter = this.boxRangeNativeHook.filter((item: NativeMemory): boolean => {
479        let filterAllocation = true;
480        //@ts-ignore
481        let freed = item.endTs > leftNs && item.endTs <= rightNs && item.endTs !== 0 && item.endTs !== null;
482        if (filterAllocType === '1') {
483          filterAllocation = !freed;
484        } else if (filterAllocType === '2') {
485          filterAllocation = freed;
486        }
487        //@ts-ignore
488        let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection);
489        let filterLastLib = filterResponseType === -1 ? true : filterResponseType === item.lastLibId;
490        return filterAllocation && filterNative && filterLastLib;
491      });
492    }
493    if (sortColumn !== undefined && sortType !== undefined && sortColumn !== '' && sortType !== 0) {
494      //@ts-ignore
495      return this.sortByNativeMemoryColumn(sortColumn, sortType, filter);
496    } else {
497      return filter;
498    }
499  }
500
501  sortByNativeMemoryColumn(nmMemoryColumn: string, nmMemorySort: number, list: Array<NativeMemory>): NativeMemory[] {
502    if (nmMemorySort === 0) {
503      return list;
504    } else {
505      return list.sort((memoryLeftData: unknown, memoryRightData: unknown): number => {
506        if (nmMemoryColumn === 'index' || nmMemoryColumn === 'startTs' || nmMemoryColumn === 'heapSize') {
507          return nmMemorySort === 1
508            ? //@ts-ignore
509            memoryLeftData[nmMemoryColumn] - memoryRightData[nmMemoryColumn]
510            : //@ts-ignore
511            memoryRightData[nmMemoryColumn] - memoryLeftData[nmMemoryColumn];
512        } else {
513          if (nmMemorySort === 1) {
514            //@ts-ignore
515            if (memoryLeftData[nmMemoryColumn] > memoryRightData[nmMemoryColumn]) {
516              return 1;
517              //@ts-ignore
518            } else if (memoryLeftData[nmMemoryColumn] === memoryRightData[nmMemoryColumn]) {
519              return 0;
520            } else {
521              return -1;
522            }
523          } else {
524            //@ts-ignore
525            if (memoryRightData[nmMemoryColumn] > memoryLeftData[nmMemoryColumn]) {
526              return 1;
527              //@ts-ignore
528            } else if (memoryLeftData[nmMemoryColumn] === memoryRightData[nmMemoryColumn]) {
529              return 0;
530            } else {
531              return -1;
532            }
533          }
534        }
535      });
536    }
537  }
538
539  groupCutFilePath(fileId: number, path: string): string {
540    let name: string;
541    if (this.dataCache.nmFileDict.has(fileId)) {
542      name = this.dataCache.nmFileDict.get(fileId) ?? '';
543    } else {
544      let currentPath = path.substring(path.lastIndexOf('/') + 1);
545      this.dataCache.nmFileDict.set(fileId, currentPath);
546      name = currentPath;
547    }
548    return name === '' ? '-' : name;
549  }
550
551  traverseSampleTree(stack: NativeHookCallInfo, hook: NativeHookStatistics): void {
552    stack.count += 1;
553    stack.countValue = `${stack.count}`;
554    stack.countPercent = `${((stack.count / this.selectTotalCount) * 100).toFixed(1)}%`;
555    stack.size += hook.heapSize;
556    stack.tid = hook.tid;
557    stack.threadName = hook.threadName;
558    stack.heapSizeStr = `${getByteWithUnit(stack.size)}`;
559    stack.heapPercent = `${((stack.size / this.selectTotalSize) * 100).toFixed(1)}%`;
560    stack.countArray.push(...(hook.countArray || hook.count));
561    stack.tsArray.push(...(hook.tsArray || hook.startTs));
562    if (stack.children.length > 0) {
563      stack.children.map((child: MerageBean): void => {
564        this.traverseSampleTree(child as NativeHookCallInfo, hook);
565      });
566    }
567  }
568  traverseTree(stack: NativeHookCallInfo, hook: NativeHookStatistics): void {
569    stack.count = 1;
570    stack.countValue = `${stack.count}`;
571    stack.countPercent = `${((stack!.count / this.selectTotalCount) * 100).toFixed(1)}%`;
572    stack.size = hook.heapSize;
573    stack.tid = hook.tid;
574    stack.threadName = hook.threadName;
575    stack.heapSizeStr = `${getByteWithUnit(stack!.size)}`;
576    stack.heapPercent = `${((stack!.size / this.selectTotalSize) * 100).toFixed(1)}%`;
577    stack.countArray.push(...(hook.countArray || hook.count));
578    stack.tsArray.push(...(hook.tsArray || hook.startTs));
579    if (stack.children.length > 0) {
580      stack.children.map((child) => {
581        this.traverseTree(child as NativeHookCallInfo, hook);
582      });
583    }
584  }
585  getTypeFromIndex(
586    indexOf: number,
587    item: NativeHookStatistics | NativeMemory,
588    statisticsSelection: Array<StatisticsSelection>
589  ): boolean {
590    if (indexOf === -1) {
591      return false;
592    }
593    if (indexOf < 3) {
594      if (indexOf === 0) {
595        return true;
596      } else if (indexOf === 1) {
597        return item.eventType === 'AllocEvent';
598      } else if (indexOf === 2) {
599        return item.eventType === 'MmapEvent';
600      }
601    } else if (indexOf - 3 < statisticsSelection.length) {
602      let selectionElement = statisticsSelection[indexOf - 3];
603      if (selectionElement.memoryTap !== undefined && selectionElement.max !== undefined) {
604        if (selectionElement.memoryTap.indexOf('Malloc') !== -1) {
605          return item.eventType === 'AllocEvent' && item.heapSize === selectionElement.max;
606        } else if (selectionElement.memoryTap.indexOf('Mmap') !== -1) {
607          return item.eventType === 'MmapEvent' && item.heapSize === selectionElement.max && item.subTypeId === null;
608        } else {
609          return item.subType === selectionElement.memoryTap;
610        }
611      }
612      if (selectionElement.max === undefined && typeof selectionElement.memoryTap === 'number') {
613        return item.subTypeId === selectionElement.memoryTap && item.eventType === 'MmapEvent';
614      }
615    }
616    return false;
617  }
618  clearAll(): void {
619    this.dataCache.clearNM();
620    this.splitMapData = {};
621    this.currentSamples = [];
622    this.allThreads = [];
623    this.queryAllCallchainsSamples = [];
624    this.realTimeDif = 0;
625    this.currentTreeMapData = {};
626    this.currentTreeList.length = 0;
627    this.responseTypes.length = 0;
628    this.boxRangeNativeHook = [];
629    this.nmArgs?.clear();
630    this.isHideThread = false;
631    this.useFreedSize = false;
632  }
633
634  queryCallchainsSamples(action: string, leftNs: number, rightNs: number, types: Array<string>): void {
635    this.queryData(
636      this.currentEventId,
637      action,
638      `select A.id,
639                callchain_id as eventId,
640                event_type as eventType,
641                heap_size as heapSize,
642                (A.start_ts - B.start_ts) as startTs,
643                (A.end_ts - B.start_ts) as endTs,
644                tid,
645                ifnull(last_lib_id,0) as lastLibId,
646                ifnull(last_symbol_id,0) as lastSymbolId,
647                t.name as threadName,
648                A.addr,
649                ifnull(A.sub_type_id, -1) as subTypeId
650            from
651                native_hook A,
652                trace_range B
653                left join
654                thread t
655                on
656                A.itid = t.id
657            where
658                A.start_ts - B.start_ts
659                between ${leftNs} and ${rightNs} and A.event_type in (${types.join(',')})
660                and A.ipid = ${this.currentSelectIPid}
661        `,
662      {}
663    );
664  }
665  queryStatisticCallchainsSamples(action: string, leftNs: number, rightNs: number, types: Array<number>): void {
666    let condition = '';
667    if (types.length === 1) {
668      if (types[0] === 0) {
669        condition = 'and type = 0';
670      } else {
671        condition = 'and type != 0';
672      }
673    }
674    let sql = `select A.id,
675                0 as tid,
676                callchain_id as eventId,
677                (case when type = 0 then 'AllocEvent' else 'MmapEvent' end) as eventType,
678                (case when sub_type_id not null then sub_type_id else type end) as subTypeId,
679                apply_size as heapSize,
680                release_size as freeSize,
681                apply_count as count,
682                release_count as freeCount,
683                (max(A.ts) - B.start_ts) as startTs,
684                ifnull(last_lib_id,0) as lastLibId,
685                ifnull(last_symbol_id,0) as lastSymbolId
686            from
687                native_hook_statistic A,
688                trace_range B
689            where
690                A.ts - B.start_ts
691                between ${leftNs} and ${rightNs}
692                ${condition}
693                and A.ipid = ${this.currentSelectIPid}
694            group by callchain_id;`;
695    this.queryData(this.currentEventId, action, sql, {});
696  }
697
698  combineStatisticAndCallChain(samples: NativeHookStatistics[]): Array<AnalysisSample> {
699    samples.sort((a, b) => a.id - b.id);
700    const analysisSampleList: Array<AnalysisSample> = [];
701    const applyAllocSamples: Array<AnalysisSample> = [];
702    const applyMmapSamples: Array<AnalysisSample> = [];
703    for (const sample of samples) {
704      const count = this.isStatisticMode ? sample.count : 1;
705      const analysisSample = new AnalysisSample(sample.id, sample.heapSize, count, sample.eventType, sample.startTs);
706      if (this.isStatisticMode) {
707        this.setStatisticSubType(analysisSample, sample);
708      } else {
709        let subType: string | undefined;
710        if (sample.subTypeId) {
711          subType = this.dataCache.dataDict.get(sample.subTypeId);
712        }
713        analysisSample.endTs = sample.endTs;
714        analysisSample.addr = sample.addr;
715        analysisSample.tid = sample.tid;
716        analysisSample.threadName = sample.threadName;
717        analysisSample.subType = subType;
718      }
719      if (['FreeEvent', 'MunmapEvent'].includes(sample.eventType)) {
720        if (sample.eventType === 'FreeEvent') {
721          this.setApplyIsRelease(analysisSample, applyAllocSamples);
722        } else {
723          this.setApplyIsRelease(analysisSample, applyMmapSamples);
724        }
725        continue;
726      } else {
727        if (sample.eventType === 'AllocEvent') {
728          applyAllocSamples.push(analysisSample);
729        } else {
730          applyMmapSamples.push(analysisSample);
731        }
732      }
733      let s = this.setAnalysisSampleArgs(analysisSample, sample);
734      analysisSampleList.push(s);
735    }
736    return analysisSampleList;
737  }
738
739  private setStatisticSubType(analysisSample: AnalysisSample, sample: NativeHookStatistics): void {
740    analysisSample.releaseCount = sample.freeCount;
741    analysisSample.releaseSize = sample.freeSize;
742    switch (sample.subTypeId) {
743      case 1:
744        analysisSample.subType = 'MmapEvent';
745        break;
746      case 2:
747        analysisSample.subType = 'FILE_PAGE_MSG';
748        break;
749      case 3:
750        analysisSample.subType = 'MEMORY_USING_MSG';
751        break;
752      default:
753        analysisSample.subType = this.dataCache.dataDict.get(sample.subTypeId);
754    }
755  }
756
757  private setAnalysisSampleArgs(analysisSample: AnalysisSample, sample: NativeHookStatistics): AnalysisSample {
758    const filePath = this.dataCache.dataDict.get(sample.lastLibId)!;
759    let libName = '';
760    if (filePath) {
761      const path = filePath.split('/');
762      libName = path[path.length - 1];
763    }
764    const symbolName = this.dataCache.dataDict.get(sample.lastSymbolId) || libName + ' (' + sample.addr + ')';
765    analysisSample.libId = sample.lastLibId || -1;
766    analysisSample.libName = libName || 'Unknown';
767    analysisSample.symbolId = sample.lastSymbolId || -1;
768    analysisSample.symbolName = symbolName || 'Unknown';
769    return analysisSample;
770  }
771
772  setApplyIsRelease(sample: AnalysisSample, arr: Array<AnalysisSample>): void {
773    let idx = arr.length - 1;
774    for (idx; idx >= 0; idx--) {
775      let item = arr[idx];
776      if (item.endTs === sample.startTs && item.addr === sample.addr) {
777        arr.splice(idx, 1);
778        item.isRelease = true;
779        return;
780      }
781    }
782  }
783
784  private freshCurrentCallchains(samples: NativeHookStatistics[], isTopDown: boolean): void {
785    this.currentTreeMapData = {};
786    this.currentTreeList = [];
787    let totalSize = 0;
788    let totalCount = 0;
789    samples.forEach((nativeHookSample: NativeHookStatistics): void => {
790      if (nativeHookSample.eventId === -1) {
791        return;
792      }
793      totalSize += nativeHookSample.heapSize;
794      totalCount += nativeHookSample.count || 1;
795      // 根据eventId拿到对应的调用栈
796      let callChains = this.createThreadSample(nativeHookSample);
797      let topIndex = isTopDown ? 0 : callChains.length - 1;
798      if (callChains.length > 0) {
799        let key = '';
800        if (this.isHideThread) {
801          key = (callChains[topIndex].symbolId || '') + '-' + (callChains[topIndex].fileId || '');
802        } else {
803          key =
804            nativeHookSample.tid +
805            '-' +
806            (callChains[topIndex].symbolId || '') +
807            '-' +
808            (callChains[topIndex].fileId || '');
809        }
810        // 有根节点的话就拿到对应的根节点 -----线程
811        let root = this.currentTreeMapData[key];
812        // 没有当前项的根节点,就new一个新的,放在currentTreeList
813        if (root === undefined) {
814          root = new NativeHookCallInfo();
815          root.threadName = nativeHookSample.threadName;
816          // 把新建的根节点加到map对象
817          this.currentTreeMapData[key] = root;
818          // 并且假草根节点数组
819          this.currentTreeList.push(root);
820        }
821        // 给顶层节点赋值,symbol,eventId,fileId等等
822        this.mergeCallChainSample(root, callChains[topIndex], nativeHookSample);
823        if (callChains.length > 1) {
824          // 递归造树结构
825          this.merageChildrenByIndex(root, callChains, topIndex, nativeHookSample, isTopDown);
826        }
827      }
828    });
829    // 合并线程级别
830    let rootMerageMap = this.mergeNodeData(totalCount, totalSize);
831    this.handleCurrentTreeList(totalCount, totalSize);
832    this.allThreads = Object.values(rootMerageMap) as NativeHookCallInfo[];
833  }
834  private mergeNodeData(totalCount: number, totalSize: number): CallInfoMap {
835    let rootMerageMap: CallInfoMap = {};
836    let threads = Object.values(this.currentTreeMapData);
837    // 遍历所有线程
838    threads.forEach((merageData: NativeHookCallInfo): void => {
839      if (this.isHideThread) {
840        merageData.tid = 0;
841        merageData.threadName = undefined;
842      }
843      // 没有父级,生成父级,把当前项放进去
844      if (rootMerageMap[merageData.tid] === undefined) {
845        let threadMerageData = new NativeHookCallInfo(); //新增进程的节点数据
846        threadMerageData.canCharge = false;
847        threadMerageData.type = -1;
848        threadMerageData.isThread = true;
849        threadMerageData.symbol = `${merageData.threadName || 'Thread'} [${merageData.tid}]`;
850        threadMerageData.children.push(merageData);
851        threadMerageData.initChildren.push(merageData);
852        threadMerageData.count = merageData.count || 1;
853        threadMerageData.heapSize = merageData.heapSize;
854        threadMerageData.totalCount = totalCount;
855        threadMerageData.totalSize = totalSize;
856        threadMerageData.tsArray = [...merageData.tsArray];
857        threadMerageData.countArray = [...merageData.countArray];
858        rootMerageMap[merageData.tid] = threadMerageData;
859      } else {
860        // 有父级,直接放进去
861        rootMerageMap[merageData.tid].children.push(merageData);
862        rootMerageMap[merageData.tid].initChildren.push(merageData);
863        rootMerageMap[merageData.tid].count += merageData.count || 1;
864        rootMerageMap[merageData.tid].heapSize += merageData.heapSize;
865        rootMerageMap[merageData.tid].totalCount = totalCount;
866        rootMerageMap[merageData.tid].totalSize = totalSize;
867        for (const count of merageData.countArray) {
868          rootMerageMap[merageData.tid].countArray.push(count);
869        }
870        for (const ts of merageData.tsArray) {
871          rootMerageMap[merageData.tid].tsArray.push(ts);
872        }
873      }
874      merageData.parentNode = rootMerageMap[merageData.tid]; //子节点添加父节点的引用
875    });
876    return rootMerageMap;
877  }
878  private handleCurrentTreeList(totalCount: number, totalSize: number): void {
879    let id = 0;
880    this.currentTreeList.forEach((nmTreeNode: NativeHookCallInfo): void => {
881      nmTreeNode.totalCount = totalCount;
882      nmTreeNode.totalSize = totalSize;
883      this.setMerageName(nmTreeNode);
884      if (nmTreeNode.id === '') {
885        nmTreeNode.id = id + '';
886        id++;
887      }
888      if (nmTreeNode.parentNode && nmTreeNode.parentNode.id === '') {
889        nmTreeNode.parentNode.id = id + '';
890        id++;
891        nmTreeNode.parentId = nmTreeNode.parentNode.id;
892      }
893    });
894  }
895  private groupCallChainSample(paramMap: Map<string, unknown>): void {
896    let filterAllocType = paramMap.get('filterAllocType') as string;
897    let filterEventType = paramMap.get('filterEventType') as string;
898    let filterResponseType = paramMap.get('filterResponseType') as number;
899    let filterAnalysis = paramMap.get('filterByTitleArr') as FilterByAnalysis;
900    if (filterAnalysis) {
901      if (filterAnalysis.type) {
902        filterEventType = filterAnalysis.type;
903      }
904      if (filterAnalysis.libId) {
905        filterResponseType = filterAnalysis.libId;
906      }
907    }
908    let libTree = paramMap?.get('filterExpression') as NativeMemoryExpression;
909    let leftNs = paramMap.get('leftNs') as number;
910    let rightNs = paramMap.get('rightNs') as number;
911    let nativeHookType = paramMap.get('nativeHookType') as string;
912    let statisticsSelection = paramMap.get('statisticsSelection') as StatisticsSelection[];
913    if (!libTree && filterAllocType === '0' && filterEventType === '0' && filterResponseType === -1) {
914      this.currentSamples = this.queryAllCallchainsSamples;
915      return;
916    }
917    this.useFreedSize = this.isStatisticMode && filterAllocType === '2';
918    let filter = this.dataFilter(
919      libTree,
920      filterAnalysis,
921      filterAllocType,
922      leftNs,
923      rightNs,
924      nativeHookType,
925      filterResponseType,
926      filterEventType,
927      statisticsSelection
928    );
929    let groupMap = this.setGroupMap(filter, filterAllocType, nativeHookType);
930    this.currentSamples = Object.values(groupMap);
931  }
932  private dataFilter(
933    libTree: NativeMemoryExpression,
934    filterAnalysis: FilterByAnalysis,
935    filterAllocType: string,
936    leftNs: number,
937    rightNs: number,
938    nativeHookType: string,
939    filterResponseType: number,
940    filterEventType: string,
941    statisticsSelection: StatisticsSelection[]
942  ): NativeHookStatistics[] {
943    return this.queryAllCallchainsSamples.filter((item: NativeHookStatistics): boolean => {
944      let filterAllocation = true;
945      if (nativeHookType === 'native-hook') {
946        filterAllocation = this.setFilterAllocation(item, filterAllocType, filterAllocation, leftNs, rightNs);
947      } else {
948        if (filterAllocType === '1') {
949          filterAllocation = item.heapSize > item.freeSize;
950        } else if (filterAllocType === '2') {
951          filterAllocation = item.freeSize > 0;
952        }
953      }
954      let filterThread = true;
955      if (filterAnalysis && filterAnalysis.tid) {
956        filterThread = item.tid === filterAnalysis.tid;
957      }
958      let filterLastLib = true;
959      if (libTree) {
960        filterLastLib = this.filterExpressionSample(item, libTree);
961        this.searchValue = '';
962      } else {
963        filterLastLib = filterResponseType === -1 ? true : filterResponseType === item.lastLibId;
964      }
965      let filterFunction = true;
966      if (filterAnalysis && filterAnalysis.symbolId) {
967        filterFunction = filterAnalysis.symbolId === item.lastSymbolId;
968      }
969      let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection);
970      return filterAllocation && filterNative && filterLastLib && filterThread && filterFunction;
971    });
972  }
973  private setFilterAllocation(
974    item: NativeHookStatistics,
975    filterAllocType: string,
976    filterAllocation: boolean,
977    leftNs: number,
978    rightNs: number
979  ): boolean {
980    if (filterAllocType === '1') {
981      filterAllocation =
982        item.startTs >= leftNs &&
983        item.startTs <= rightNs &&
984        (item.endTs > rightNs || item.endTs === 0 || item.endTs === null);
985    } else if (filterAllocType === '2') {
986      filterAllocation =
987        item.startTs >= leftNs &&
988        item.startTs <= rightNs &&
989        item.endTs <= rightNs &&
990        item.endTs !== 0 &&
991        item.endTs !== null;
992    }
993    return filterAllocation;
994  }
995  private setGroupMap(
996    filter: Array<NativeHookStatistics>,
997    filterAllocType: string,
998    nativeHookType: string
999  ): StatisticMap {
1000    let groupMap: StatisticMap = {};
1001    filter.forEach((sample: NativeHookStatistics): void => {
1002      let currentNode = groupMap[sample.tid + '-' + sample.eventId] || new NativeHookStatistics();
1003      if (currentNode.count === 0) {
1004        Object.assign(currentNode, sample);
1005        if (filterAllocType === '1' && nativeHookType !== 'native-hook') {
1006          currentNode.heapSize = sample.heapSize - sample.freeSize;
1007          currentNode.count = sample.count - sample.freeCount;
1008        }
1009        if (currentNode.count === 0) {
1010          currentNode.count++;
1011          currentNode.countArray.push(1);
1012          currentNode.tsArray.push(sample.startTs);
1013        }
1014      } else {
1015        currentNode.count++;
1016        currentNode.heapSize += sample.heapSize;
1017        currentNode.countArray.push(1);
1018        currentNode.tsArray.push(sample.startTs);
1019      }
1020      groupMap[sample.tid + '-' + sample.eventId] = currentNode;
1021    });
1022    return groupMap;
1023  }
1024
1025  private filterExpressionSample(sample: NativeHookStatistics, expressStruct: NativeMemoryExpression): boolean {
1026    const itemLibName = this.dataCache.dataDict.get(sample.lastLibId);
1027    const itemSymbolName = this.dataCache.dataDict.get(sample.lastSymbolId);
1028    if (!itemLibName || !itemSymbolName) {
1029      return false;
1030    }
1031
1032    function isMatch(libTree: Map<string, string[]>, match: boolean): boolean {
1033      for (const [lib, symbols] of libTree) {
1034        // lib不包含则跳过
1035        if (!itemLibName!.toLowerCase().includes(lib.toLowerCase()) && lib !== '*') {
1036          continue;
1037        }
1038        // * 表示全量
1039        if (symbols.includes('*')) {
1040          match = true;
1041          break;
1042        }
1043
1044        for (const symbol of symbols) {
1045          // 匹配到了就返回
1046          if (itemSymbolName!.toLowerCase().includes(symbol.toLowerCase())) {
1047            match = true;
1048            break;
1049          }
1050        }
1051        // 如果匹配到了,跳出循环
1052        if (match) {
1053          break;
1054        }
1055        //全部没有匹配到
1056        match = false;
1057      }
1058      return match;
1059    }
1060
1061    let includeMatch = expressStruct.includeLib.size === 0; // true表达这条数据需要显示
1062    includeMatch = isMatch(expressStruct.includeLib, includeMatch);
1063
1064    if (expressStruct.abandonLib.size === 0) {
1065      return includeMatch;
1066    }
1067
1068    let abandonMatch = false; // false表示这条数据需要显示
1069    abandonMatch = isMatch(expressStruct.abandonLib, abandonMatch);
1070
1071    return includeMatch && !abandonMatch;
1072  }
1073
1074  createThreadSample(sample: NativeHookStatistics): HeapTreeDataBean[] {
1075    return this.dataCache.nmHeapFrameMap.get(sample.eventId) || [];
1076  }
1077
1078  mergeCallChainSample(
1079    currentNode: NativeHookCallInfo,
1080    callChain: HeapTreeDataBean,
1081    sample: NativeHookStatistics
1082  ): void {
1083    if (currentNode.symbol === undefined || currentNode.symbol === '') {
1084      currentNode.symbol = callChain.AllocationFunction || '';
1085      currentNode.addr = callChain.addr;
1086      currentNode.eventId = sample.eventId;
1087      currentNode.eventType = sample.eventType;
1088      currentNode.symbolId = callChain.symbolId;
1089      currentNode.fileId = callChain.fileId;
1090      currentNode.tid = sample.tid;
1091    }
1092    if (this.useFreedSize) {
1093      currentNode.count += sample.freeCount || 1;
1094    } else {
1095      currentNode.count += sample.count || 1;
1096    }
1097
1098    if (sample.countArray && sample.countArray.length > 0) {
1099      currentNode.countArray = currentNode.countArray.concat(sample.countArray);
1100    } else {
1101      currentNode.countArray.push(sample.count);
1102    }
1103
1104    if (sample.tsArray && sample.tsArray.length > 0) {
1105      currentNode.tsArray = currentNode.tsArray.concat(sample.tsArray);
1106    } else {
1107      currentNode.tsArray.push(sample.startTs);
1108    }
1109    if (this.useFreedSize) {
1110      currentNode.heapSize += sample.freeSize;
1111    } else {
1112      currentNode.heapSize += sample.heapSize;
1113    }
1114  }
1115
1116  merageChildrenByIndex(
1117    currentNode: NativeHookCallInfo,
1118    callChainDataList: HeapTreeDataBean[],
1119    index: number,
1120    sample: NativeHookStatistics,
1121    isTopDown: boolean
1122  ): void {
1123    isTopDown ? index++ : index--;
1124    let isEnd = isTopDown ? callChainDataList.length === index + 1 : index === 0;
1125    let node: NativeHookCallInfo;
1126    if (
1127      //@ts-ignore
1128      currentNode.initChildren.filter((child: NativeHookCallInfo): boolean => {
1129        if (
1130          child.symbolId === callChainDataList[index]?.symbolId &&
1131          child.fileId === callChainDataList[index]?.fileId
1132        ) {
1133          node = child;
1134          this.mergeCallChainSample(child, callChainDataList[index], sample);
1135          return true;
1136        }
1137        return false;
1138      }).length === 0
1139    ) {
1140      node = new NativeHookCallInfo();
1141      this.mergeCallChainSample(node, callChainDataList[index], sample);
1142      currentNode.children.push(node);
1143      currentNode.initChildren.push(node);
1144      // 将所有节点存到this.currentTreeList
1145      this.currentTreeList.push(node);
1146      node.parentNode = currentNode;
1147    }
1148    if (node! && !isEnd) {
1149      this.merageChildrenByIndex(node, callChainDataList, index, sample, isTopDown);
1150    }
1151  }
1152
1153  private extractSymbolAndPath(node: NativeHookCallInfo, str?: string): void {
1154    node.symbol = 'unknown';
1155    if (!str) {
1156      return;
1157    }
1158    const match = str.match(/^([^\[:]+):\[url:(.+)\]$/);
1159    if (!match) {
1160      return;
1161    }
1162    node.symbol = match[1].trim();
1163    node.lib = match[2].replace(/^url:/, '');
1164  }
1165
1166  private isHap(path: string): boolean {
1167    for (const name of HAP_TYPE) {
1168      if (path.endsWith(name)) {
1169        return true;
1170      }
1171    }
1172    return false;
1173  }
1174
1175  setMerageName(currentNode: NativeHookCallInfo): void {
1176    currentNode.lib = this.dataCache.dataDict.get(currentNode.fileId) || 'unknown';
1177    if (this.isHap(currentNode.lib)) {
1178      const fullName = this.dataCache.dataDict.get(currentNode.symbolId);
1179      this.extractSymbolAndPath(currentNode, fullName);
1180    } else {
1181      currentNode.symbol =
1182        this.groupCutFilePath(currentNode.symbolId, this.dataCache.dataDict.get(currentNode.symbolId) || '') ??
1183        'unknown';
1184    }
1185    currentNode.path = currentNode.lib;
1186    currentNode.lib = setFileName(currentNode.lib);
1187    currentNode.symbol = `${currentNode.symbol} (${currentNode.lib})`;
1188    currentNode.type =
1189      currentNode.lib.endsWith('.so.1') || currentNode.lib.endsWith('.dll') || currentNode.lib.endsWith('.so') ? 0 : 1;
1190  }
1191  clearSplitMapData(symbolName: string): void {
1192    if (symbolName in this.splitMapData) {
1193      Reflect.deleteProperty(this.splitMapData, symbolName);
1194    }
1195  }
1196  resolvingNMCallAction(params: unknown[]): NativeHookCallInfo[] {
1197    if (params.length > 0) {
1198      params.forEach((item: unknown): void => {
1199        //@ts-ignore
1200        let funcName = item.funcName;
1201        //@ts-ignore
1202        let args = item.funcArgs;
1203        if (funcName && args) {
1204          this.handleDataByFuncName(funcName, args);
1205        }
1206      });
1207    }
1208    return this.allThreads.filter((thread: NativeHookCallInfo): boolean => {
1209      return thread.children && thread.children.length > 0;
1210    });
1211  }
1212  handleDataByFuncName(funcName: string, args: unknown[]): void {
1213    switch (funcName) {
1214      case 'hideThread':
1215        this.isHideThread = args[0] as boolean;
1216        break;
1217      case 'groupCallchainSample':
1218        this.groupCallChainSample(args[0] as Map<string, unknown>);
1219        break;
1220      case 'getCallChainsBySampleIds':
1221        this.freshCurrentCallchains(this.currentSamples, args[0] as boolean);
1222        break;
1223      case 'hideSystemLibrary':
1224        merageBeanDataSplit.hideSystemLibrary(this.allThreads, this.splitMapData);
1225        break;
1226      case 'hideNumMaxAndMin':
1227        merageBeanDataSplit.hideNumMaxAndMin(this.allThreads, this.splitMapData, args[0] as number, args[1] as string);
1228        break;
1229      case 'splitAllProcess':
1230        merageBeanDataSplit.splitAllProcess(this.allThreads, this.splitMapData, args[0]);
1231        break;
1232      case 'resetAllNode':
1233        merageBeanDataSplit.resetAllNode(this.allThreads, this.currentTreeList, this.searchValue);
1234        break;
1235      case 'resotreAllNode':
1236        merageBeanDataSplit.resotreAllNode(this.splitMapData, args[0] as string[]);
1237        break;
1238      case 'splitTree':
1239        merageBeanDataSplit.splitTree(
1240          this.splitMapData,
1241          this.allThreads,
1242          args[0] as string,
1243          args[1] as boolean,
1244          args[2] as boolean,
1245          this.currentTreeList,
1246          this.searchValue
1247        );
1248        break;
1249      case 'setSearchValue':
1250        this.searchValue = args[0] as string;
1251        break;
1252      case 'clearSplitMapData':
1253        this.clearSplitMapData(args[0] as string);
1254        break;
1255    }
1256  }
1257}
1258
1259export class NativeHookStatistics {
1260  id: number = 0;
1261  eventId: number = 0;
1262  eventType: string = '';
1263  subType: string = '';
1264  subTypeId: number = 0;
1265  heapSize: number = 0;
1266  freeSize: number = 0;
1267  addr: string = '';
1268  startTs: number = 0;
1269  endTs: number = 0;
1270  sumHeapSize: number = 0;
1271  max: number = 0;
1272  count: number = 0;
1273  freeCount: number = 0;
1274  tid: number = 0;
1275  threadName: string = '';
1276  lastLibId: number = 0;
1277  lastSymbolId: number = 0;
1278  isSelected: boolean = false;
1279  tsArray: Array<number> = [];
1280  countArray: Array<number> = [];
1281}
1282export class NativeHookCallInfo extends MerageBean {
1283  #totalCount: number = 0;
1284  #totalSize: number = 0;
1285  symbolId: number = 0;
1286  fileId: number = 0;
1287  count: number = 0;
1288  countValue: string = '';
1289  countPercent: string = '';
1290  type: number = 0;
1291  heapSize: number = 0;
1292  heapPercent: string = '';
1293  heapSizeStr: string = '';
1294  eventId: number = 0;
1295  tid: number = 0;
1296  threadName: string | undefined = '';
1297  eventType: string = '';
1298  isSelected: boolean = false;
1299  set totalCount(total: number) {
1300    this.#totalCount = total;
1301    this.countValue = `${this.count}`;
1302    this.size = this.heapSize;
1303    this.countPercent = `${((this.count / total) * 100).toFixed(1)}%`;
1304  }
1305  get totalCount(): number {
1306    return this.#totalCount;
1307  }
1308  set totalSize(total: number) {
1309    this.#totalSize = total;
1310    this.heapSizeStr = `${getByteWithUnit(this.heapSize)}`;
1311    this.heapPercent = `${((this.heapSize / total) * 100).toFixed(1)}%`;
1312  }
1313  get totalSize(): number {
1314    return this.#totalSize;
1315  }
1316}
1317export class NativeMemory {
1318  index: number = 0;
1319  eventId: number = 0;
1320  eventType: string = '';
1321  subType: string = '';
1322  subTypeId: number = 0;
1323  addr: string = '';
1324  startTs: number = 0;
1325  endTs: number = 0;
1326  heapSize: number = 0;
1327  symbol: string = '';
1328  library: string = '';
1329  lastLibId: number = 0;
1330  lastSymbolId: number = 0;
1331  isSelected: boolean = false;
1332  threadId: number = 0;
1333}
1334
1335export class HeapStruct {
1336  startTime: number | undefined;
1337  endTime: number | undefined;
1338  dur: number | undefined;
1339  density: number | undefined;
1340  heapsize: number | undefined;
1341  maxHeapSize: number = 0;
1342  maxDensity: number = 0;
1343  minHeapSize: number = 0;
1344  minDensity: number = 0;
1345}
1346export class StatisticsSelection {
1347  memoryTap: string = '';
1348  max: number = 0;
1349}
1350
1351class AnalysisSample {
1352  id: number;
1353  count: number;
1354  size: number;
1355  type: number;
1356  startTs: number;
1357
1358  isRelease: boolean;
1359  releaseCount?: number;
1360  releaseSize?: number;
1361
1362  endTs?: number;
1363  subType?: string;
1364  tid?: number;
1365  threadName?: string;
1366  addr?: string;
1367
1368  libId!: number;
1369  libName!: string;
1370  symbolId!: number;
1371  symbolName!: string;
1372
1373  constructor(id: number, size: number, count: number, type: number | string, startTs: number) {
1374    this.id = id;
1375    this.size = size;
1376    this.count = count;
1377    this.startTs = startTs;
1378    switch (type) {
1379      case 'AllocEvent':
1380      case '0':
1381        this.type = 0;
1382        this.isRelease = false;
1383        break;
1384      case 'MmapEvent':
1385      case '1':
1386        this.isRelease = false;
1387        this.type = 1;
1388        break;
1389      case 'FreeEvent':
1390        this.isRelease = true;
1391        this.type = 2;
1392        break;
1393      case 'MunmapEvent':
1394        this.isRelease = true;
1395        this.type = 3;
1396        break;
1397      default:
1398        this.isRelease = false;
1399        this.type = -1;
1400    }
1401  }
1402}
1403