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