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