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