• 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 */
15
16import {ChartStruct, convertJSON, getByteWithUnit, getTimeString, LogicHandler} from "./ProcedureLogicWorkerCommon.js";
17
18
19export class ProcedureLogicWorkerNativeMemory extends LogicHandler {
20    selectTotalSize = 0;
21    selectTotalCount = 0;
22    stackCount = 0;
23    DATA_DICT: Map<number, string> = new Map<number, string>();
24    HEAP_FRAME_MAP: Map<number,Array<HeapTreeDataBean>> = new Map<number, Array<HeapTreeDataBean>>();
25    HEAP_FRAME_STACK: Map<number, NativeHookCallInfo> = new Map<number, NativeHookCallInfo>();
26    NATIVE_MEMORY_DATA: Array<NativeEvent> = [];
27    currentEventId: string = ""
28
29    handle(data: any): void {
30        this.currentEventId = data.id
31        if (data && data.type) {
32            switch (data.type) {
33                case "native-memory-init":
34                    this.clearAll();
35                    this.initDataDict();
36                    break
37                case "native-memory-queryDataDICT":
38                    let dict = convertJSON(data.params.list) || []
39                    dict.map((d: any) => this.DATA_DICT.set(d['id'], d['data']));
40                    this.initNMChartData();
41                    break;
42                case "native-memory-queryNMChartData":
43                    this.NATIVE_MEMORY_DATA = convertJSON(data.params.list) || [];
44                    this.initNMFrameData();
45                    break;
46                case "native-memory-queryNMFrameData":
47                    let arr = convertJSON(data.params.list) || [];
48                    this.initNMStack(arr);
49                    arr = [];
50                    self.postMessage({
51                        id: data.id,
52                        action: "native-memory-init",
53                        results: []
54                    });
55                    break;
56                case "native-memory-action":
57                    if (data.params) {
58                        // @ts-ignore
59                        self.postMessage({
60                            id: data.id,
61                            action: data.action,
62                            results: this.resolvingAction(data.params)
63                        });
64                    }
65                    break;
66            }
67        }
68    }
69
70    queryData(queryName: string, sql: string, args: any) {
71        self.postMessage({
72            id: this.currentEventId,
73            type: queryName,
74            isQuery: true,
75            args: args,
76            sql: sql
77        })
78    }
79
80    initDataDict() {
81        this.queryData("native-memory-queryDataDICT", `select * from data_dict;`, {})
82    }
83
84    initNMChartData() {
85        this.queryData("native-memory-queryNMChartData", `
86            select * from (
87                select
88                    h.start_ts - t.start_ts as startTime,
89                    h.heap_size as heapSize,
90                    h.event_type as eventType
91                from native_hook h ,trace_range t
92                where h.start_ts >= t.start_ts and h.start_ts <= t.end_ts and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent')
93                union
94                select
95                    h.end_ts - t.start_ts as startTime,
96                    h.heap_size as heapSize,
97                    (case when h.event_type = 'AllocEvent' then 'FreeEvent' else 'MunmapEvent' end) as eventType
98                from native_hook h ,trace_range t
99                where h.start_ts >= t.start_ts and h.start_ts <= t.end_ts
100                    and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent')
101                    and h.end_ts not null ) order by startTime;
102        `, {})
103    }
104
105    initNMFrameData() {
106        this.queryData("native-memory-queryNMFrameData", `
107            select h.symbol_id as symbolId, h.file_id as fileId, h.depth, h.callchain_id as eventId
108                    from native_hook_frame h
109        `, {})
110    }
111
112    initNMStack(frameArr:Array<HeapTreeDataBean>){
113        frameArr.map((frame) => {
114            let sym_arr = (this.DATA_DICT.get(frame.symbolId) ?? "").split("/");
115            let lib_arr = (this.DATA_DICT.get(frame.fileId) ?? "").split("/");
116            frame.AllocationFunction = sym_arr![sym_arr!.length - 1];
117            frame.MoudleName = lib_arr![lib_arr!.length - 1];
118            let frameEventId = parseInt(frame.eventId);
119            if(this.HEAP_FRAME_MAP.has(frameEventId)){
120                this.HEAP_FRAME_MAP.get(frameEventId)!.push(frame);
121            }else{
122                this.HEAP_FRAME_MAP.set(frameEventId,[frame])
123            }
124            let target = new NativeHookCallInfo();
125            target.id = frame.eventId + "_" + frame.depth;
126            target.eventId = frameEventId;
127            target.depth = frame.depth;
128            target.count = 1;
129            target.symbol = frame.AllocationFunction;
130            target.symbolId = frame.symbolId;
131            target.library = frame.MoudleName;
132            target.title = `[ ${target.symbol} ]  ${target.library}`;
133            target.type = (target.library.endsWith(".so.1") || target.library.endsWith(".dll") || target.library.endsWith(".so")) ? 0 : 1;
134            if (this.HEAP_FRAME_STACK.has(frameEventId)) {
135                let src = this.HEAP_FRAME_STACK.get(frameEventId);
136                this.listToTree(target, src!);
137            } else {
138                this.HEAP_FRAME_STACK.set(frameEventId, target);
139            }
140        })
141    }
142
143    resolvingAction(paramMap: Map<string, any>): Array<NativeHookCallInfo | NativeMemory | HeapStruct> {
144        let actionType = paramMap.get("actionType");
145        if (actionType == "call-info") {
146            return this.resolvingActionCallInfo(paramMap);
147        } else if (actionType == "native-memory") {
148            return this.resolvingActionNativeMemory(paramMap);
149        } else if (actionType == "memory-stack") {
150            return this.resolvingActionNativeMemoryStack(paramMap);
151        } else if (actionType == "memory-chart") {
152            return this.resolvingActionNativeMemoryChartData(paramMap);
153        } else {
154            return []
155        }
156    }
157
158    resolvingActionNativeMemoryChartData(paramMap: Map<string, any>): Array<HeapStruct> {
159        let nativeMemoryType: number = paramMap.get("nativeMemoryType") as number;
160        let chartType: number = paramMap.get("chartType") as number;
161        let totalNS: number = paramMap.get("totalNS") as number;
162        let arr: Array<HeapStruct> = [];
163        let source: Array<NativeEvent> = [];
164        if (nativeMemoryType == 0) {
165            source = this.NATIVE_MEMORY_DATA;
166        } else if (nativeMemoryType == 1) {
167            this.NATIVE_MEMORY_DATA.map((ne) => {
168                if (ne.eventType == 'AllocEvent' || ne.eventType == 'FreeEvent') {
169                    source.push(ne);
170                }
171            })
172        } else {
173            this.NATIVE_MEMORY_DATA.map((ne) => {
174                if (ne.eventType == 'MmapEvent' || ne.eventType == 'MunmapEvent') {
175                    source.push(ne);
176                }
177            })
178        }
179        if (source.length > 0) {
180            let first = new HeapStruct();
181            first.startTime = source[0].startTime;
182            first.eventType = source[0].eventType;
183            if (first.eventType == "AllocEvent" || first.eventType == "MmapEvent") {
184                first.heapsize = chartType == 1 ? 1 : source[0].heapSize;
185            } else {
186                first.heapsize = chartType == 1 ? -1 : (0 - source[0].heapSize);
187            }
188            arr.push(first);
189            let max = first.heapsize;
190            let min = first.heapsize;
191            for (let i = 1, len = source.length; i < len; i++) {
192                let heap = new HeapStruct();
193                heap.startTime = source[i].startTime;
194                heap.eventType = source[i].eventType;
195                arr[i - 1].dur = heap.startTime! - arr[i - 1].startTime!;
196                if (i == len - 1) {
197                    heap.dur = totalNS - heap.startTime!;
198                }
199                if (heap.eventType == "AllocEvent" || heap.eventType == "MmapEvent") {
200                    if (chartType == 1) {
201                        heap.heapsize = arr[i - 1].heapsize! + 1;
202                    } else {
203                        heap.heapsize = arr[i - 1].heapsize! + source[i].heapSize;
204                    }
205                } else {
206                    if (chartType == 1) {
207                        heap.heapsize = arr[i - 1].heapsize! - 1;
208                    } else {
209                        heap.heapsize = arr[i - 1].heapsize! - source[i].heapSize;
210                    }
211                }
212                if (heap.heapsize > max) {
213                    max = heap.heapsize;
214                }
215                if (heap.heapsize < min) {
216                    min = heap.heapsize;
217                }
218                arr.push(heap);
219            }
220            arr.map((heap) => {
221                heap.maxHeapSize = max;
222                heap.minHeapSize = min;
223            })
224        }
225        return arr;
226    }
227
228    resolvingActionNativeMemoryStack(paramMap: Map<string, any>) {
229        let eventId = paramMap.get("eventId");
230        let frameArr = this.HEAP_FRAME_MAP.get(eventId) || [];
231        let arr: Array<NativeHookCallInfo> = [];
232        frameArr.map((frame) => {
233            let target = new NativeHookCallInfo();
234            target.eventId = parseInt(frame.eventId);
235            target.depth = frame.depth;
236            target.symbol = frame.AllocationFunction ?? "";
237            target.library = frame.MoudleName ?? "";
238            target.title = `[ ${target.symbol} ]  ${target.library}`;
239            target.type = (target.library.endsWith(".so.1") || target.library.endsWith(".dll") || target.library.endsWith(".so")) ? 0 : 1;
240            arr.push(target);
241        })
242        return arr;
243    }
244
245    resolvingActionNativeMemory(paramMap: Map<string, any>): Array<NativeMemory> {
246        let dataSource = paramMap.get("data") as Array<NativeHookStatistics>;
247        let filterAllocType = paramMap.get("filterAllocType");
248        let filterEventType = paramMap.get("filterEventType");
249        let leftNs = paramMap.get("leftNs");
250        let rightNs = paramMap.get("rightNs");
251        let statisticsSelection = paramMap.get("statisticsSelection");
252        let filter = dataSource.filter((item) => {
253            let filterAllocation = true
254            if (filterAllocType == "1") {
255                filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs
256                    && (item.endTs > rightNs || item.endTs == 0 || item.endTs == null)
257            } else if (filterAllocType == "2") {
258                filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs
259                    && item.endTs <= rightNs && item.endTs != 0 && item.endTs != null;
260            }
261            let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection)
262            return filterAllocation && filterNative
263        })
264        let data: Array<NativeMemory> = [];
265        for (let i = 0, len = filter.length; i < len; i++) {
266            let hook = filter[i];
267            let memory = new NativeMemory();
268            memory.index = i;
269            memory.eventId = hook.eventId;
270            memory.eventType = hook.eventType;
271            memory.subType = hook.subType;
272            memory.heapSize = hook.heapSize;
273            memory.endTs = hook.endTs;
274            memory.heapSizeUnit = getByteWithUnit(hook.heapSize);
275            memory.addr = "0x" + hook.addr;
276            memory.startTs = hook.startTs;
277            memory.timestamp = getTimeString(hook.startTs);
278            memory.state = (hook.endTs > leftNs && hook.endTs <= rightNs) ? "Freed" : "Existing";
279            memory.threadId = hook.tid;
280            memory.threadName = hook.threadName;
281            (memory as any).isSelected = hook.isSelected;
282            let arr = this.HEAP_FRAME_MAP.get(hook.eventId) || []
283            let frame = Array.from(arr).reverse().find((item) => !((item.MoudleName ?? "").includes("libc++") || (item.MoudleName ?? "").includes("musl")))
284            if (frame != null && frame != undefined) {
285                memory.symbol = frame.AllocationFunction ?? "";
286                memory.library = frame.MoudleName ?? "Unknown Path";
287            }
288            data.push(memory);
289        }
290        return data
291    }
292
293    resolvingActionCallInfo(paramMap: Map<string, any>): Array<NativeHookCallInfo> {
294        let dataSource = paramMap.get("data") as Array<NativeHookStatistics>;
295        let filterAllocType = paramMap.get("filterAllocType");
296        let filterEventType = paramMap.get("filterEventType");
297        let leftNs = paramMap.get("leftNs");
298        let rightNs = paramMap.get("rightNs");
299        let filter: Array<NativeHookStatistics> = [];
300        dataSource.map((item) => {
301            let filterAllocation = true;
302            let filterNative = true;
303            if (filterAllocType == "1") {
304                filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs
305                    && (item.endTs > rightNs || item.endTs == 0 || item.endTs == null)
306            } else if (filterAllocType == "2") {
307                filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs
308                    && item.endTs <= rightNs && item.endTs != 0 && item.endTs != null;
309            }
310            if (filterEventType == "1") {
311                filterNative = item.eventType == "AllocEvent"
312            } else if (filterEventType == "2") {
313                filterNative = item.eventType == "MmapEvent"
314            }
315            if (filterAllocation && filterNative) {
316                filter.push(item);
317            }
318        })
319        this.selectTotalSize = 0;
320        this.selectTotalCount = filter.length
321        let map = new Map<number, NativeHookCallInfo>();
322        filter.map((r) => {
323            this.selectTotalSize += r.heapSize;
324        });
325        filter.map((r) => {
326            let callStack = this.HEAP_FRAME_STACK.get(r.eventId);
327            if (callStack != null && callStack != undefined) {
328                this.traverseTree(callStack, r);
329                if (map.has(r.eventId)) {
330                    let stack = map.get(r.eventId)
331                    this.traverseSampleTree(stack!, r);
332                } else {
333                    map.set(r.eventId, JSON.parse(JSON.stringify(callStack)))
334                }
335            }
336        })
337        let groupMap = new Map<string, Array<NativeHookCallInfo>>();
338        for (let value of map.values()) {
339            let key = value.threadId + "_" + value.symbol;
340            if (groupMap.has(key)) {
341                groupMap.get(key)!.push(value);
342            } else {
343                let arr: Array<NativeHookCallInfo> = [];
344                arr.push(value);
345                groupMap.set(key, arr);
346            }
347        }
348        let stackArr = Array.from(groupMap.values());
349        let data: Array<NativeHookCallInfo> = [];
350        for (let arr of stackArr) {
351            if (arr.length > 1) {
352                for (let i = 1; i < arr.length; i++) {
353                    arr[0].count += arr[i].count;
354                    if (arr[i].children.length > 0) {
355                        this.mergeTree(<NativeHookCallInfo>arr[i].children[0], arr[0]);
356                    } else {
357                        arr[0].size += arr[i].size;
358                        arr[0].heapSizeStr = `${getByteWithUnit(arr[0]!.size)}`;
359                        arr[0].heapPercent = `${(arr[0]!.size / this.selectTotalSize * 100).toFixed(1)}%`
360                    }
361                }
362            } else {
363                arr[0].count = arr[0].count;
364            }
365            arr[0]!.countValue = `${arr[0].count}`
366            arr[0]!.countPercent = `${(arr[0]!.count / this.selectTotalCount * 100).toFixed(1)}%`
367            data.push(arr[0]);
368        }
369        return this.groupByWithTid(data);
370    }
371
372    groupByWithTid(data: Array<NativeHookCallInfo>): Array<NativeHookCallInfo> {
373        let tidMap = new Map<number, NativeHookCallInfo>();
374        for (let call of data) {
375            call.pid = "tid_" + call.threadId;
376            if (tidMap.has(call.threadId)) {
377                let tidCall = tidMap.get(call.threadId);
378                tidCall!.size += call.size;
379                tidCall!.heapSizeStr = `${getByteWithUnit(tidCall!.size)}`;
380                tidCall!.heapPercent = `${(tidCall!.size / this.selectTotalSize * 100).toFixed(1)}%`
381                tidCall!.count += call.count;
382                tidCall!.countValue = `${tidCall!.count}`
383                tidCall!.countPercent = `${(tidCall!.count / this.selectTotalCount * 100).toFixed(1)}%`
384                tidCall!.children.push(call);
385            } else {
386                let tidCall = new NativeHookCallInfo();
387                tidCall.id = "tid_" + call.threadId;
388                tidCall.count = call.count;
389                tidCall!.countValue = `${call.count}`
390                tidCall!.countPercent = `${(tidCall!.count / this.selectTotalCount * 100).toFixed(1)}%`
391                tidCall.size = call.size;
392                tidCall.heapSizeStr = `${getByteWithUnit(tidCall!.size)}`;
393                tidCall!.heapPercent = `${(tidCall!.size / this.selectTotalSize * 100).toFixed(1)}%`
394                tidCall.title = (call.threadName == null ? 'Thread' : call.threadName) + " [ " + call.threadId + " ]";
395                tidCall.symbol = tidCall.title;
396                tidCall.type = -1;
397                tidCall.children.push(call);
398                tidMap.set(call.threadId, tidCall);
399            }
400        }
401        let showData = Array.from(tidMap.values())
402        return showData;
403    }
404
405    mergeTree(target: NativeHookCallInfo, src: NativeHookCallInfo) {
406        let len = src.children.length;
407        src.size += target.size;
408        src.heapSizeStr = `${getByteWithUnit(src!.size)}`;
409        src.heapPercent = `${(src!.size / this.selectTotalSize * 100).toFixed(1)}%`
410        if (len == 0) {
411            target.pid = src.id;
412            src.children.push(target);
413        } else {
414            let index = src.children.findIndex((hook) => hook.symbol == target.symbol && hook.depth == target.depth);
415            if (index != -1) {
416                let srcChild = <NativeHookCallInfo>src.children[index];
417                srcChild.count += target.count;
418                srcChild!.countValue = `${srcChild.count}`
419                srcChild!.countPercent = `${(srcChild!.count / this.selectTotalCount * 100).toFixed(1)}%`
420                if (target.children.length > 0) {
421                    this.mergeTree(<NativeHookCallInfo>target.children[0], <NativeHookCallInfo>srcChild)
422                } else {
423                    srcChild.size += target.size;
424                    srcChild.heapSizeStr = `${getByteWithUnit(src!.size)}`;
425                    srcChild.heapPercent = `${(srcChild!.size / this.selectTotalSize * 100).toFixed(1)}%`
426                }
427            } else {
428                target.pid = src.id;
429                src.children.push(target)
430            }
431        }
432    }
433
434    traverseSampleTree(stack: NativeHookCallInfo, hook: NativeHookStatistics) {
435        stack.count += 1;
436        stack.countValue = `${stack.count}`
437        stack.countPercent = `${(stack.count / this.selectTotalCount * 100).toFixed(1)}%`
438        stack.size += hook.heapSize;
439        stack.threadId = hook.tid;
440        stack.threadName = hook.threadName;
441        stack.heapSizeStr = `${getByteWithUnit(stack.size)}`;
442        stack.heapPercent = `${(stack.size / this.selectTotalSize * 100).toFixed(1)}%`;
443        if (stack.children.length > 0) {
444            stack.children.map((child) => {
445                this.traverseSampleTree(child as NativeHookCallInfo, hook);
446            })
447        }
448    }
449
450    traverseTree(stack: NativeHookCallInfo, hook: NativeHookStatistics) {
451        stack.count = 1;
452        stack.countValue = `${stack.count}`
453        stack.countPercent = `${(stack!.count / this.selectTotalCount * 100).toFixed(1)}%`
454        stack.size = hook.heapSize;
455        stack.threadId = hook.tid;
456        stack.threadName = hook.threadName;
457        stack.heapSizeStr = `${getByteWithUnit(stack!.size)}`;
458        stack.heapPercent = `${(stack!.size / this.selectTotalSize * 100).toFixed(1)}%`;
459        if (stack.children.length > 0) {
460            stack.children.map((child) => {
461                this.traverseTree(child as NativeHookCallInfo, hook);
462            })
463        }
464    }
465
466    getTypeFromIndex(indexOf: number, item: NativeHookStatistics, statisticsSelection: Array<StatisticsSelection>): boolean {
467        if (indexOf == -1) {
468            return false;
469        }
470        if (indexOf < 3) {
471            if (indexOf == 0) {
472                return true
473            } else if (indexOf == 1) {
474                return item.eventType == "AllocEvent"
475            } else if (indexOf == 2) {
476                return item.eventType == "MmapEvent"
477            }
478        } else if (indexOf - 3 < statisticsSelection.length) {
479            let selectionElement = statisticsSelection[indexOf - 3];
480            if (selectionElement.memoryTap != undefined && selectionElement.max != undefined) {
481                if (selectionElement.memoryTap.indexOf("Malloc") != -1) {
482                    return item.eventType == "AllocEvent" && item.heapSize == selectionElement.max
483                } else if (selectionElement.memoryTap.indexOf("Mmap") != -1) {
484                    return item.eventType == "MmapEvent" && item.heapSize == selectionElement.max
485                } else {
486                    return item.subType == selectionElement.memoryTap && item.heapSize == selectionElement.max
487                }
488            }
489        }
490        return false;
491    }
492
493    clearAll() {
494        this.DATA_DICT.clear();
495        this.HEAP_FRAME_MAP.clear();
496        this.NATIVE_MEMORY_DATA = [];
497        this.HEAP_FRAME_STACK.clear();
498    }
499
500    listToTree(target: NativeHookCallInfo, src: NativeHookCallInfo) {
501        if (target.depth == src.depth + 1) {
502            target.pid = src.id;
503            src.children.push(target)
504        } else {
505            if (src.children.length > 0) {
506                this.listToTree(target, <NativeHookCallInfo>src.children[0]);
507            }
508        }
509    }
510}
511
512export class HeapTreeDataBean {
513    MoudleName: string | undefined
514    AllocationFunction: string | undefined
515    symbolId: number = 0
516    fileId: number = 0
517    startTs: number = 0
518    endTs: number = 0
519    eventType: string | undefined
520    depth: number = 0
521    heapSize: number = 0
522    eventId: string = ""
523}
524
525export class NativeHookStatistics {
526    eventId: number = 0;
527    eventType: string = "";
528    subType: string = "";
529    subTypeId: number = 0;
530    heapSize: number = 0;
531    addr: string = "";
532    startTs: number = 0;
533    endTs: number = 0;
534    sumHeapSize: number = 0;
535    max: number = 0;
536    count: number = 0;
537    tid: number = 0;
538    threadName: string = "";
539    isSelected: boolean = false;
540}
541
542export class NativeHookCallInfo extends ChartStruct {
543    id: string = "";
544    pid: string | undefined;
545    library: string = "";
546    symbolId: number = 0;
547    title: string = "";
548    count: number = 0;
549    countValue: string = ""
550    countPercent: string = "";
551    type: number = 0;
552    heapSize: number = 0;
553    heapPercent: string = "";
554    heapSizeStr: string = "";
555    eventId: number = 0;
556    threadId: number = 0;
557    threadName: string = "";
558    isSelected: boolean = false;
559}
560
561export class NativeMemory {
562    index: number = 0;
563    eventId: number = 0;
564    eventType: string = "";
565    subType: string = "";
566    addr: string = "";
567    startTs: number = 0;
568    endTs: number = 0;
569    timestamp: string = ""
570    heapSize: number = 0;
571    heapSizeUnit: string = "";
572    symbol: string = "";
573    library: string = "";
574    isSelected: boolean = false;
575    state: string = "";
576    threadId: number = 0;
577    threadName: string = "";
578}
579
580export class HeapStruct {
581    startTime: number | undefined
582    endTime: number | undefined
583    dur: number | undefined
584    eventType: string | undefined
585    heapsize: number | undefined
586    maxHeapSize: number = 0
587    minHeapSize: number = 0
588}
589
590export class NativeEvent {
591    startTime: number = 0;
592    heapSize: number = 0;
593    eventType: string = "";
594}
595
596export class StatisticsSelection {
597    memoryTap: string = "";
598    max: number = 0;
599}