• 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 */
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      let callChains = this.createThreadSample(nativeHookSample);
796      let topIndex = isTopDown ? 0 : callChains.length - 1;
797      if (callChains.length > 0) {
798        let key = '';
799        if (this.isHideThread) {
800          key = (callChains[topIndex].symbolId || '') + '-' + (callChains[topIndex].fileId || '');
801        } else {
802          key =
803            nativeHookSample.tid +
804            '-' +
805            (callChains[topIndex].symbolId || '') +
806            '-' +
807            (callChains[topIndex].fileId || '');
808        }
809        let root = this.currentTreeMapData[key];
810        if (root === undefined) {
811          root = new NativeHookCallInfo();
812          root.threadName = nativeHookSample.threadName;
813          this.currentTreeMapData[key] = root;
814          this.currentTreeList.push(root);
815        }
816        this.mergeCallChainSample(root, callChains[topIndex], nativeHookSample);
817        if (callChains.length > 1) {
818          this.merageChildrenByIndex(root, callChains, topIndex, nativeHookSample, isTopDown);
819        }
820      }
821    });
822    let rootMerageMap = this.mergeNodeData(totalCount, totalSize);
823    this.handleCurrentTreeList(totalCount, totalSize);
824    this.allThreads = Object.values(rootMerageMap) as NativeHookCallInfo[];
825  }
826  private mergeNodeData(totalCount: number, totalSize: number): CallInfoMap {
827    let rootMerageMap: CallInfoMap = {};
828    let threads = Object.values(this.currentTreeMapData);
829    threads.forEach((merageData: NativeHookCallInfo): void => {
830      if (this.isHideThread) {
831        merageData.tid = 0;
832        merageData.threadName = undefined;
833      }
834      if (rootMerageMap[merageData.tid] === undefined) {
835        let threadMerageData = new NativeHookCallInfo(); //新增进程的节点数据
836        threadMerageData.canCharge = false;
837        threadMerageData.type = -1;
838        threadMerageData.isThread = true;
839        threadMerageData.symbol = `${merageData.threadName || 'Thread'} [${merageData.tid}]`;
840        threadMerageData.children.push(merageData);
841        threadMerageData.initChildren.push(merageData);
842        threadMerageData.count = merageData.count || 1;
843        threadMerageData.heapSize = merageData.heapSize;
844        threadMerageData.totalCount = totalCount;
845        threadMerageData.totalSize = totalSize;
846        threadMerageData.tsArray = [...merageData.tsArray];
847        threadMerageData.countArray = [...merageData.countArray];
848        rootMerageMap[merageData.tid] = threadMerageData;
849      } else {
850        rootMerageMap[merageData.tid].children.push(merageData);
851        rootMerageMap[merageData.tid].initChildren.push(merageData);
852        rootMerageMap[merageData.tid].count += merageData.count || 1;
853        rootMerageMap[merageData.tid].heapSize += merageData.heapSize;
854        rootMerageMap[merageData.tid].totalCount = totalCount;
855        rootMerageMap[merageData.tid].totalSize = totalSize;
856        for (const count of merageData.countArray) {
857          rootMerageMap[merageData.tid].countArray.push(count);
858        }
859        for (const ts of merageData.tsArray) {
860          rootMerageMap[merageData.tid].tsArray.push(ts);
861        }
862      }
863      merageData.parentNode = rootMerageMap[merageData.tid]; //子节点添加父节点的引用
864    });
865    return rootMerageMap;
866  }
867  private handleCurrentTreeList(totalCount: number, totalSize: number): void {
868    let id = 0;
869    this.currentTreeList.forEach((nmTreeNode: NativeHookCallInfo): void => {
870      nmTreeNode.totalCount = totalCount;
871      nmTreeNode.totalSize = totalSize;
872      this.setMerageName(nmTreeNode);
873      if (nmTreeNode.id === '') {
874        nmTreeNode.id = id + '';
875        id++;
876      }
877      if (nmTreeNode.parentNode) {
878        if (nmTreeNode.parentNode.id === '') {
879          nmTreeNode.parentNode.id = id + '';
880          id++;
881        }
882        nmTreeNode.parentId = nmTreeNode.parentNode.id;
883      }
884    });
885  }
886  private groupCallChainSample(paramMap: Map<string, unknown>): void {
887    let filterAllocType = paramMap.get('filterAllocType') as string;
888    let filterEventType = paramMap.get('filterEventType') as string;
889    let filterResponseType = paramMap.get('filterResponseType') as number;
890    let filterAnalysis = paramMap.get('filterByTitleArr') as FilterByAnalysis;
891    if (filterAnalysis) {
892      if (filterAnalysis.type) {
893        filterEventType = filterAnalysis.type;
894      }
895      if (filterAnalysis.libId) {
896        filterResponseType = filterAnalysis.libId;
897      }
898    }
899    let libTree = paramMap?.get('filterExpression') as NativeMemoryExpression;
900    let leftNs = paramMap.get('leftNs') as number;
901    let rightNs = paramMap.get('rightNs') as number;
902    let nativeHookType = paramMap.get('nativeHookType') as string;
903    let statisticsSelection = paramMap.get('statisticsSelection') as StatisticsSelection[];
904    if (!libTree && filterAllocType === '0' && filterEventType === '0' && filterResponseType === -1) {
905      this.currentSamples = this.queryAllCallchainsSamples;
906      return;
907    }
908    this.useFreedSize = this.isStatisticMode && filterAllocType === '2';
909    let filter = this.dataFilter(
910      libTree,
911      filterAnalysis,
912      filterAllocType,
913      leftNs,
914      rightNs,
915      nativeHookType,
916      filterResponseType,
917      filterEventType,
918      statisticsSelection
919    );
920    let groupMap = this.setGroupMap(filter, filterAllocType, nativeHookType);
921    this.currentSamples = Object.values(groupMap);
922  }
923  private dataFilter(
924    libTree: NativeMemoryExpression,
925    filterAnalysis: FilterByAnalysis,
926    filterAllocType: string,
927    leftNs: number,
928    rightNs: number,
929    nativeHookType: string,
930    filterResponseType: number,
931    filterEventType: string,
932    statisticsSelection: StatisticsSelection[]
933  ): NativeHookStatistics[] {
934    return this.queryAllCallchainsSamples.filter((item: NativeHookStatistics): boolean => {
935      let filterAllocation = true;
936      if (nativeHookType === 'native-hook') {
937        filterAllocation = this.setFilterAllocation(item, filterAllocType, filterAllocation, leftNs, rightNs);
938      } else {
939        if (filterAllocType === '1') {
940          filterAllocation = item.heapSize > item.freeSize;
941        } else if (filterAllocType === '2') {
942          filterAllocation = item.freeSize > 0;
943        }
944      }
945      let filterThread = true;
946      if (filterAnalysis && filterAnalysis.tid) {
947        filterThread = item.tid === filterAnalysis.tid;
948      }
949      let filterLastLib = true;
950      if (libTree) {
951        filterLastLib = this.filterExpressionSample(item, libTree);
952        this.searchValue = '';
953      } else {
954        filterLastLib = filterResponseType === -1 ? true : filterResponseType === item.lastLibId;
955      }
956      let filterFunction = true;
957      if (filterAnalysis && filterAnalysis.symbolId) {
958        filterFunction = filterAnalysis.symbolId === item.lastSymbolId;
959      }
960      let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection);
961      return filterAllocation && filterNative && filterLastLib && filterThread && filterFunction;
962    });
963  }
964  private setFilterAllocation(
965    item: NativeHookStatistics,
966    filterAllocType: string,
967    filterAllocation: boolean,
968    leftNs: number,
969    rightNs: number
970  ): boolean {
971    if (filterAllocType === '1') {
972      filterAllocation =
973        item.startTs >= leftNs &&
974        item.startTs <= rightNs &&
975        (item.endTs > rightNs || item.endTs === 0 || item.endTs === null);
976    } else if (filterAllocType === '2') {
977      filterAllocation =
978        item.startTs >= leftNs &&
979        item.startTs <= rightNs &&
980        item.endTs <= rightNs &&
981        item.endTs !== 0 &&
982        item.endTs !== null;
983    }
984    return filterAllocation;
985  }
986  private setGroupMap(
987    filter: Array<NativeHookStatistics>,
988    filterAllocType: string,
989    nativeHookType: string
990  ): StatisticMap {
991    let groupMap: StatisticMap = {};
992    filter.forEach((sample: NativeHookStatistics): void => {
993      let currentNode = groupMap[sample.tid + '-' + sample.eventId] || new NativeHookStatistics();
994      if (currentNode.count === 0) {
995        Object.assign(currentNode, sample);
996        if (filterAllocType === '1' && nativeHookType !== 'native-hook') {
997          currentNode.heapSize = sample.heapSize - sample.freeSize;
998          currentNode.count = sample.count - sample.freeCount;
999        }
1000        if (currentNode.count === 0) {
1001          currentNode.count++;
1002          currentNode.countArray.push(1);
1003          currentNode.tsArray.push(sample.startTs);
1004        }
1005      } else {
1006        currentNode.count++;
1007        currentNode.heapSize += sample.heapSize;
1008        currentNode.countArray.push(1);
1009        currentNode.tsArray.push(sample.startTs);
1010      }
1011      groupMap[sample.tid + '-' + sample.eventId] = currentNode;
1012    });
1013    return groupMap;
1014  }
1015
1016  private filterExpressionSample(sample: NativeHookStatistics, expressStruct: NativeMemoryExpression): boolean {
1017    const itemLibName = this.dataCache.dataDict.get(sample.lastLibId);
1018    const itemSymbolName = this.dataCache.dataDict.get(sample.lastSymbolId);
1019    if (!itemLibName || !itemSymbolName) {
1020      return false;
1021    }
1022
1023    function isMatch(libTree: Map<string, string[]>, match: boolean): boolean {
1024      for (const [lib, symbols] of libTree) {
1025        // lib不包含则跳过
1026        if (!itemLibName!.toLowerCase().includes(lib.toLowerCase()) && lib !== '*') {
1027          continue;
1028        }
1029        // * 表示全量
1030        if (symbols.includes('*')) {
1031          match = true;
1032          break;
1033        }
1034
1035        for (const symbol of symbols) {
1036          // 匹配到了就返回
1037          if (itemSymbolName!.toLowerCase().includes(symbol.toLowerCase())) {
1038            match = true;
1039            break;
1040          }
1041        }
1042        // 如果匹配到了,跳出循环
1043        if (match) {
1044          break;
1045        }
1046        //全部没有匹配到
1047        match = false;
1048      }
1049      return match;
1050    }
1051
1052    let includeMatch = expressStruct.includeLib.size === 0; // true表达这条数据需要显示
1053    includeMatch = isMatch(expressStruct.includeLib, includeMatch);
1054
1055    if (expressStruct.abandonLib.size === 0) {
1056      return includeMatch;
1057    }
1058
1059    let abandonMatch = false; // false表示这条数据需要显示
1060    abandonMatch = isMatch(expressStruct.abandonLib, abandonMatch);
1061
1062    return includeMatch && !abandonMatch;
1063  }
1064
1065  createThreadSample(sample: NativeHookStatistics): HeapTreeDataBean[] {
1066    return this.dataCache.nmHeapFrameMap.get(sample.eventId) || [];
1067  }
1068
1069  mergeCallChainSample(
1070    currentNode: NativeHookCallInfo,
1071    callChain: HeapTreeDataBean,
1072    sample: NativeHookStatistics
1073  ): void {
1074    if (currentNode.symbol === undefined || currentNode.symbol === '') {
1075      currentNode.symbol = callChain.AllocationFunction || '';
1076      currentNode.addr = callChain.addr;
1077      currentNode.eventId = sample.eventId;
1078      currentNode.eventType = sample.eventType;
1079      currentNode.symbolId = callChain.symbolId;
1080      currentNode.fileId = callChain.fileId;
1081      currentNode.tid = sample.tid;
1082    }
1083    if (this.useFreedSize) {
1084      currentNode.count += sample.freeCount || 1;
1085    } else {
1086      currentNode.count += sample.count || 1;
1087    }
1088
1089    if (sample.countArray && sample.countArray.length > 0) {
1090      currentNode.countArray = currentNode.countArray.concat(sample.countArray);
1091    } else {
1092      currentNode.countArray.push(sample.count);
1093    }
1094
1095    if (sample.tsArray && sample.tsArray.length > 0) {
1096      currentNode.tsArray = currentNode.tsArray.concat(sample.tsArray);
1097    } else {
1098      currentNode.tsArray.push(sample.startTs);
1099    }
1100    if (this.useFreedSize) {
1101      currentNode.heapSize += sample.freeSize;
1102    } else {
1103      currentNode.heapSize += sample.heapSize;
1104    }
1105  }
1106
1107  merageChildrenByIndex(
1108    currentNode: NativeHookCallInfo,
1109    callChainDataList: HeapTreeDataBean[],
1110    index: number,
1111    sample: NativeHookStatistics,
1112    isTopDown: boolean
1113  ): void {
1114    isTopDown ? index++ : index--;
1115    let isEnd = isTopDown ? callChainDataList.length === index + 1 : index === 0;
1116    let node: NativeHookCallInfo;
1117    if (
1118      //@ts-ignore
1119      currentNode.initChildren.filter((child: NativeHookCallInfo): boolean => {
1120        if (
1121          child.symbolId === callChainDataList[index]?.symbolId &&
1122          child.fileId === callChainDataList[index]?.fileId
1123        ) {
1124          node = child;
1125          this.mergeCallChainSample(child, callChainDataList[index], sample);
1126          return true;
1127        }
1128        return false;
1129      }).length === 0
1130    ) {
1131      node = new NativeHookCallInfo();
1132      this.mergeCallChainSample(node, callChainDataList[index], sample);
1133      currentNode.children.push(node);
1134      currentNode.initChildren.push(node);
1135      this.currentTreeList.push(node);
1136      node.parentNode = currentNode;
1137    }
1138    if (node! && !isEnd) {
1139      this.merageChildrenByIndex(node, callChainDataList, index, sample, isTopDown);
1140    }
1141  }
1142
1143  private extractSymbolAndPath(node: NativeHookCallInfo, str?: string): void {
1144    node.symbol = 'unknown';
1145    if (!str) {
1146      return;
1147    }
1148    const match = str.match(/^([^\[:]+):\[url:(.+)\]$/);
1149    if (!match) {
1150      return;
1151    }
1152    node.symbol = match[1].trim();
1153    node.lib = match[2].replace(/^url:/, '');
1154  }
1155
1156  private isHap(path: string): boolean {
1157    for (const name of HAP_TYPE) {
1158      if (path.endsWith(name)) {
1159        return true;
1160      }
1161    }
1162    return false;
1163  }
1164
1165  setMerageName(currentNode: NativeHookCallInfo): void {
1166    currentNode.lib = this.dataCache.dataDict.get(currentNode.fileId) || 'unknown';
1167    if (this.isHap(currentNode.lib)) {
1168      const fullName = this.dataCache.dataDict.get(currentNode.symbolId);
1169      this.extractSymbolAndPath(currentNode, fullName);
1170    } else {
1171      currentNode.symbol =
1172        this.groupCutFilePath(currentNode.symbolId, this.dataCache.dataDict.get(currentNode.symbolId) || '') ??
1173        'unknown';
1174    }
1175    currentNode.path = currentNode.lib;
1176    currentNode.lib = setFileName(currentNode.lib);
1177    currentNode.symbol = `${currentNode.symbol} (${currentNode.lib})`;
1178    currentNode.type =
1179      currentNode.lib.endsWith('.so.1') || currentNode.lib.endsWith('.dll') || currentNode.lib.endsWith('.so') ? 0 : 1;
1180  }
1181  clearSplitMapData(symbolName: string): void {
1182    if (symbolName in this.splitMapData) {
1183      Reflect.deleteProperty(this.splitMapData, symbolName);
1184    }
1185  }
1186  resolvingNMCallAction(params: unknown[]): NativeHookCallInfo[] {
1187    if (params.length > 0) {
1188      params.forEach((item: unknown): void => {
1189        //@ts-ignore
1190        let funcName = item.funcName;
1191        //@ts-ignore
1192        let args = item.funcArgs;
1193        if (funcName && args) {
1194          this.handleDataByFuncName(funcName, args);
1195        }
1196      });
1197    }
1198    return this.allThreads.filter((thread: NativeHookCallInfo): boolean => {
1199      return thread.children && thread.children.length > 0;
1200    });
1201  }
1202  handleDataByFuncName(funcName: string, args: unknown[]): void {
1203    switch (funcName) {
1204      case 'hideThread':
1205        this.isHideThread = args[0] as boolean;
1206        break;
1207      case 'groupCallchainSample':
1208        this.groupCallChainSample(args[0] as Map<string, unknown>);
1209        break;
1210      case 'getCallChainsBySampleIds':
1211        this.freshCurrentCallchains(this.currentSamples, args[0] as boolean);
1212        break;
1213      case 'hideSystemLibrary':
1214        merageBeanDataSplit.hideSystemLibrary(this.allThreads, this.splitMapData);
1215        break;
1216      case 'hideNumMaxAndMin':
1217        merageBeanDataSplit.hideNumMaxAndMin(this.allThreads, this.splitMapData, args[0] as number, args[1] as string);
1218        break;
1219      case 'splitAllProcess':
1220        merageBeanDataSplit.splitAllProcess(this.allThreads, this.splitMapData, args[0]);
1221        break;
1222      case 'resetAllNode':
1223        merageBeanDataSplit.resetAllNode(this.allThreads, this.currentTreeList, this.searchValue);
1224        break;
1225      case 'resotreAllNode':
1226        merageBeanDataSplit.resotreAllNode(this.splitMapData, args[0] as string[]);
1227        break;
1228      case 'splitTree':
1229        merageBeanDataSplit.splitTree(
1230          this.splitMapData,
1231          this.allThreads,
1232          args[0] as string,
1233          args[1] as boolean,
1234          args[2] as boolean,
1235          this.currentTreeList,
1236          this.searchValue
1237        );
1238        break;
1239      case 'setSearchValue':
1240        this.searchValue = args[0] as string;
1241        break;
1242      case 'clearSplitMapData':
1243        this.clearSplitMapData(args[0] as string);
1244        break;
1245    }
1246  }
1247}
1248
1249export class NativeHookStatistics {
1250  id: number = 0;
1251  eventId: number = 0;
1252  eventType: string = '';
1253  subType: string = '';
1254  subTypeId: number = 0;
1255  heapSize: number = 0;
1256  freeSize: number = 0;
1257  addr: string = '';
1258  startTs: number = 0;
1259  endTs: number = 0;
1260  sumHeapSize: number = 0;
1261  max: number = 0;
1262  count: number = 0;
1263  freeCount: number = 0;
1264  tid: number = 0;
1265  threadName: string = '';
1266  lastLibId: number = 0;
1267  lastSymbolId: number = 0;
1268  isSelected: boolean = false;
1269  tsArray: Array<number> = [];
1270  countArray: Array<number> = [];
1271}
1272export class NativeHookCallInfo extends MerageBean {
1273  #totalCount: number = 0;
1274  #totalSize: number = 0;
1275  symbolId: number = 0;
1276  fileId: number = 0;
1277  count: number = 0;
1278  countValue: string = '';
1279  countPercent: string = '';
1280  type: number = 0;
1281  heapSize: number = 0;
1282  heapPercent: string = '';
1283  heapSizeStr: string = '';
1284  eventId: number = 0;
1285  tid: number = 0;
1286  threadName: string | undefined = '';
1287  eventType: string = '';
1288  isSelected: boolean = false;
1289  set totalCount(total: number) {
1290    this.#totalCount = total;
1291    this.countValue = `${this.count}`;
1292    this.size = this.heapSize;
1293    this.countPercent = `${((this.count / total) * 100).toFixed(1)}%`;
1294  }
1295  get totalCount(): number {
1296    return this.#totalCount;
1297  }
1298  set totalSize(total: number) {
1299    this.#totalSize = total;
1300    this.heapSizeStr = `${getByteWithUnit(this.heapSize)}`;
1301    this.heapPercent = `${((this.heapSize / total) * 100).toFixed(1)}%`;
1302  }
1303  get totalSize(): number {
1304    return this.#totalSize;
1305  }
1306}
1307export class NativeMemory {
1308  index: number = 0;
1309  eventId: number = 0;
1310  eventType: string = '';
1311  subType: string = '';
1312  subTypeId: number = 0;
1313  addr: string = '';
1314  startTs: number = 0;
1315  endTs: number = 0;
1316  heapSize: number = 0;
1317  symbol: string = '';
1318  library: string = '';
1319  lastLibId: number = 0;
1320  lastSymbolId: number = 0;
1321  isSelected: boolean = false;
1322  threadId: number = 0;
1323}
1324
1325export class HeapStruct {
1326  startTime: number | undefined;
1327  endTime: number | undefined;
1328  dur: number | undefined;
1329  density: number | undefined;
1330  heapsize: number | undefined;
1331  maxHeapSize: number = 0;
1332  maxDensity: number = 0;
1333  minHeapSize: number = 0;
1334  minDensity: number = 0;
1335}
1336export class StatisticsSelection {
1337  memoryTap: string = '';
1338  max: number = 0;
1339}
1340
1341class AnalysisSample {
1342  id: number;
1343  count: number;
1344  size: number;
1345  type: number;
1346  startTs: number;
1347
1348  isRelease: boolean;
1349  releaseCount?: number;
1350  releaseSize?: number;
1351
1352  endTs?: number;
1353  subType?: string;
1354  tid?: number;
1355  threadName?: string;
1356  addr?: string;
1357
1358  libId!: number;
1359  libName!: string;
1360  symbolId!: number;
1361  symbolName!: string;
1362
1363  constructor(id: number, size: number, count: number, type: number | string, startTs: number) {
1364    this.id = id;
1365    this.size = size;
1366    this.count = count;
1367    this.startTs = startTs;
1368    switch (type) {
1369      case 'AllocEvent':
1370      case '0':
1371        this.type = 0;
1372        this.isRelease = false;
1373        break;
1374      case 'MmapEvent':
1375      case '1':
1376        this.isRelease = false;
1377        this.type = 1;
1378        break;
1379      case 'FreeEvent':
1380        this.isRelease = true;
1381        this.type = 2;
1382        break;
1383      case 'MunmapEvent':
1384        this.isRelease = true;
1385        this.type = 3;
1386        break;
1387      default:
1388        this.isRelease = false;
1389        this.type = -1;
1390    }
1391  }
1392}
1393