• 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
16class TraceWorkerNativeMemory {
17    selectTotalSize = 0;
18    selectTotalCount = 0;
19    stackCount = 0;
20    DATA_DICT: Map<number, string> = new Map<number, string>();
21    HEAP_FRAME_DATA: Array<HeapTreeDataBean> = [];
22    HEAP_FRAME_STACK: Map<number, NativeHookCallInfo> = new Map<number, NativeHookCallInfo>();
23
24    initNativeMemory(queryFunc: Function) {
25        this.clearAll()
26        let dict = queryFunc("queryDataDICT", `select * from data_dict;`)
27        dict.map((d: any) => this.DATA_DICT.set(d['id'], d['data']));
28        let res = queryFunc("queryHeapAllTable", `select count(*) as count from native_hook_frame `, {})
29        let count = 0;
30        if (res != undefined && res.length > 0 && (res[0] as any).count != undefined) {
31            count = (res[0] as any).count;
32        }
33        if (count > 0) {
34            let pageSize = 300000;
35            let pages = Math.ceil(count / pageSize);
36            for (let i = 0; i < pages; i++) {
37                let arr = queryFunc("queryHeapAllTable", `select h.symbol_id as symbolId, h.file_id as fileId, h.depth, h.eventId
38                    from native_hook_frame h limit $limit offset $offset`, {$limit: pageSize, $offset: i * pageSize});
39                this.HEAP_FRAME_DATA = this.HEAP_FRAME_DATA.concat(arr);
40            }
41        }
42        this.HEAP_FRAME_DATA.map((frame) => {
43            frame.AllocationFunction = this.DATA_DICT.get(frame.symbolId)
44            frame.MoudleName = this.DATA_DICT.get(frame.fileId)
45            let frameEventId = parseInt(frame.eventId);
46            let target = new NativeHookCallInfo();
47            target.id = frame.eventId + "_" + frame.depth;
48            target.eventId = frameEventId;
49            target.depth = frame.depth;
50            target.count = 1;
51            let sym_arr = frame.AllocationFunction?.split("/");
52            let lib_arr = frame.MoudleName?.split("/");
53            target.symbol = sym_arr![sym_arr!.length - 1];
54            target.symbolId = frame.symbolId;
55            target.library = lib_arr![lib_arr!.length - 1];
56            target.title = `[ ${target.symbol} ]  ${target.library}`;
57            target.type = (target.library.endsWith(".so.1") || target.library.endsWith(".dll") || target.library.endsWith(".so")) ? 0 : 1;
58            if (this.HEAP_FRAME_STACK.has(frameEventId)) {
59                let src = this.HEAP_FRAME_STACK.get(frameEventId);
60                this.listToTree(target, src!);
61            } else {
62                this.HEAP_FRAME_STACK.set(frameEventId, target);
63            }
64        })
65    }
66
67    resolvingAction(paramMap: Map<string, any>): Array<NativeHookCallInfo | NativeMemory> {
68        let actionType = paramMap.get("actionType");
69        if (actionType == "call-info") {
70            return this.resolvingActionCallInfo(paramMap);
71        } else if(actionType == "native-memory"){
72            return this.resolvingActionNativeMemory(paramMap);
73        } else if(actionType == "memory-stack"){
74            return this.resolvingActionNativeMemoryStack(paramMap);
75        }else{
76            return []
77        }
78    }
79
80    resolvingActionNativeMemoryStack(paramMap: Map<string, any>){
81        let eventId = paramMap.get("eventId");
82        let frameArr = this.HEAP_FRAME_DATA.filter((frame) => parseInt(frame.eventId) == eventId);
83        let arr: Array<NativeHookCallInfo> = [];
84        frameArr.map((frame) => {
85            let target = new NativeHookCallInfo();
86            target.eventId = parseInt(frame.eventId);
87            target.depth = frame.depth;
88            let sym_arr = frame.AllocationFunction?.split("/");
89            let lib_arr = frame.MoudleName?.split("/");
90            target.symbol = sym_arr![sym_arr!.length - 1];
91            target.library = lib_arr![lib_arr!.length - 1];
92            target.title = `[ ${target.symbol} ]  ${target.library}`;
93            target.type = (target.library.endsWith(".so.1") || target.library.endsWith(".dll") || target.library.endsWith(".so")) ? 0 : 1;
94            arr.push(target);
95        })
96        return arr;
97    }
98
99    resolvingActionNativeMemory(paramMap: Map<string, any>): Array<NativeMemory> {
100        let dataSource = paramMap.get("data") as Array<NativeHookStatistics>;
101        let filterAllocType = paramMap.get("filterAllocType");
102        let filterEventType = paramMap.get("filterEventType");
103        let leftNs = paramMap.get("leftNs");
104        let rightNs = paramMap.get("rightNs");
105        let statisticsSelection = paramMap.get("statisticsSelection");
106        let filter = dataSource.filter((item) => {
107            let filterAllocation = true
108            if (filterAllocType == "1") {
109                filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs
110                    && (item.endTs > rightNs || item.endTs == 0 || item.endTs == null)
111            } else if (filterAllocType == "2") {
112                filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs
113                    && item.endTs <= rightNs && item.endTs != 0 && item.endTs != null;
114            }
115            let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item,statisticsSelection)
116            return filterAllocation && filterNative
117        })
118        let frameMap = new Map<number, NativeHookCallInfo>();
119        filter.map((r) => {
120            let callStack = this.HEAP_FRAME_STACK.get(r.eventId);
121            if (callStack != null && callStack != undefined) {
122                frameMap.set(r.eventId, callStack);
123            }
124        })
125        let data: Array<NativeMemory> = [];
126        for (let i = 0, len = filter.length; i < len; i++) {
127            let hook = filter[i];
128            let memory = new NativeMemory();
129            memory.index = i;
130            memory.eventId = hook.eventId;
131            memory.eventType = hook.eventType;
132            memory.subType = hook.subType;
133            memory.heapSize = hook.heapSize;
134            memory.endTs = hook.endTs;
135            memory.heapSizeUnit = this.getByteWithUnit(hook.heapSize);
136            memory.addr = "0x" + hook.addr;
137            memory.startTs = hook.startTs;
138            memory.timestamp = this.getTimeString(hook.startTs);
139            memory.state = (hook.endTs > leftNs && hook.endTs <= rightNs) ? "Freed" : "Existing";
140            memory.threadId = hook.tid;
141            memory.threadName = hook.threadName;
142            (memory as any).isSelected = hook.isSelected;
143            let frame = frameMap.get(hook.eventId);
144            if (frame != null && frame != undefined) {
145                memory.symbol = frame.symbol;
146                memory.library = frame.library;
147            }
148            data.push(memory);
149        }
150        return data
151    }
152
153    resolvingActionCallInfo(paramMap: Map<string, any>): Array<NativeHookCallInfo> {
154        let dataSource = paramMap.get("data") as Array<NativeHookStatistics>;
155        let filterAllocType = paramMap.get("filterAllocType");
156        let filterEventType = paramMap.get("filterEventType");
157        let leftNs = paramMap.get("leftNs");
158        let rightNs = paramMap.get("rightNs");
159
160        let filter: Array<NativeHookStatistics> = [];
161        dataSource.map((item) => {
162            let filterAllocation = true;
163            let filterNative = true;
164            if (filterAllocType == "1") {
165                filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs
166                    && (item.endTs > rightNs || item.endTs == 0 || item.endTs == null)
167            } else if (filterAllocType == "2") {
168                filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs
169                    && item.endTs <= rightNs && item.endTs != 0 && item.endTs != null;
170            }
171            if (filterEventType == "1") {
172                filterNative = item.eventType == "AllocEvent"
173            } else if (filterEventType == "2") {
174                filterNative = item.eventType == "MmapEvent"
175            }
176            if (filterAllocation && filterNative) {
177                filter.push(item);
178            }
179        })
180        this.selectTotalSize = 0;
181        this.selectTotalCount = filter.length
182        let map = new Map<number, NativeHookCallInfo>();
183        filter.map((r) => {
184            this.selectTotalSize += r.heapSize;
185        });
186        filter.map((r) => {
187            let callStack = this.HEAP_FRAME_STACK.get(r.eventId);
188            if (callStack != null && callStack != undefined) {
189                this.traverseTree(callStack, r);
190                map.set(r.eventId, JSON.parse(JSON.stringify(callStack)))
191            }
192        })
193        let groupMap = new Map<string, Array<NativeHookCallInfo>>();
194        for (let value of map.values()) {
195            let key = value.threadId + "_" + value.symbol;
196            if (groupMap.has(key)) {
197                groupMap.get(key)!.push(value);
198            } else {
199                let arr: Array<NativeHookCallInfo> = [];
200                arr.push(value);
201                groupMap.set(key, arr);
202            }
203        }
204        let stackArr = Array.from(groupMap.values());
205        let data: Array<NativeHookCallInfo> = [];
206        for (let arr of stackArr) {
207            if (arr.length > 1) {
208                for (let i = 1; i < arr.length; i++) {
209                    if (arr[i].children.length > 0) {
210                        this.mergeTree(<NativeHookCallInfo>arr[i].children[0], arr[0]);
211                    } else {
212                        arr[0].size += arr[i].size;
213                        arr[0].heapSizeStr = `${this.getByteWithUnit(arr[0]!.size)}`;
214                        arr[0].heapPercent = `${(arr[0]!.size / this.selectTotalSize * 100).toFixed(1)}%`
215                    }
216                }
217            }
218            arr[0].count = arr.length;
219            arr[0]!.countValue = `${arr[0].count}`
220            arr[0]!.countPercent = `${(arr[0]!.count / this.selectTotalCount * 100).toFixed(1)}%`
221            data.push(arr[0]);
222        }
223        return this.groupByWithTid(data);
224    }
225
226    groupByWithTid(data: Array<NativeHookCallInfo>): Array<NativeHookCallInfo> {
227        let tidMap = new Map<number, NativeHookCallInfo>();
228        for (let call of data) {
229            call.pid = "tid_" + call.threadId;
230            if (tidMap.has(call.threadId)) {
231                let tidCall = tidMap.get(call.threadId);
232                tidCall!.size += call.size;
233                tidCall!.heapSizeStr = `${this.getByteWithUnit(tidCall!.size)}`;
234                tidCall!.heapPercent = `${(tidCall!.size / this.selectTotalSize * 100).toFixed(1)}%`
235                tidCall!.count += call.count;
236                tidCall!.countValue = `${tidCall!.count}`
237                tidCall!.countPercent = `${(tidCall!.count / this.selectTotalCount * 100).toFixed(1)}%`
238                tidCall!.children.push(call);
239            } else {
240                let tidCall = new NativeHookCallInfo();
241                tidCall.id = "tid_" + call.threadId;
242                tidCall.count = call.count;
243                tidCall!.countValue = `${call.count}`
244                tidCall!.countPercent = `${(tidCall!.count / this.selectTotalCount * 100).toFixed(1)}%`
245                tidCall.size = call.size;
246                tidCall.heapSizeStr = `${this.getByteWithUnit(tidCall!.size)}`;
247                tidCall!.heapPercent = `${(tidCall!.size / this.selectTotalSize * 100).toFixed(1)}%`
248                tidCall.title = (call.threadName == null ? 'Thread' : call.threadName) + " [ " + call.threadId + " ]";
249                tidCall.symbol = tidCall.title;
250                tidCall.type = -1;
251                tidCall.children.push(call);
252                tidMap.set(call.threadId, tidCall);
253            }
254        }
255        let showData = Array.from(tidMap.values())
256        return showData;
257    }
258
259    mergeTree(target: NativeHookCallInfo, src: NativeHookCallInfo) {
260        let len = src.children.length;
261        src.size += target.size;
262        src.heapSizeStr = `${this.getByteWithUnit(src!.size)}`;
263        src.heapPercent = `${(src!.size / this.selectTotalSize * 100).toFixed(1)}%`
264        if (len == 0) {
265            target.pid = src.id;
266            src.children.push(target);
267        } else {
268            let index = src.children.findIndex((hook) => hook.symbol == target.symbol && hook.depth == target.depth);
269            if (index != -1) {
270                let srcChild = <NativeHookCallInfo>src.children[index];
271                srcChild.count += 1;
272                srcChild!.countValue = `${srcChild.count}`
273                srcChild!.countPercent = `${(srcChild!.count / this.selectTotalCount * 100).toFixed(1)}%`
274                if (target.children.length > 0) {
275                    this.mergeTree(<NativeHookCallInfo>target.children[0], <NativeHookCallInfo>srcChild)
276                } else {
277                    srcChild.size += target.size;
278                    srcChild.heapSizeStr = `${this.getByteWithUnit(src!.size)}`;
279                    srcChild.heapPercent = `${(srcChild!.size / this.selectTotalSize * 100).toFixed(1)}%`
280                }
281            } else {
282                target.pid = src.id;
283                src.children.push(target)
284            }
285        }
286    }
287
288    traverseTree(stack: NativeHookCallInfo, hook: NativeHookStatistics) {
289        stack.count = 1;
290        stack.countValue = `${stack.count}`
291        stack.countPercent = `${(stack!.count / this.selectTotalCount * 100).toFixed(1)}%`
292        stack.size = hook.heapSize;
293        stack.threadId = hook.tid;
294        stack.threadName = hook.threadName;
295        stack.heapSizeStr = `${this.getByteWithUnit(stack!.size)}`;
296        stack.heapPercent = `${(stack!.size / this.selectTotalSize * 100).toFixed(1)}%`;
297        if (stack.children.length > 0) {
298            stack.children.map((child) => {
299                this.traverseTree(child as NativeHookCallInfo, hook);
300            })
301        }
302    }
303
304    getTypeFromIndex(indexOf: number, item: NativeHookStatistics,statisticsSelection:Array<StatisticsSelection>): boolean {
305        if (indexOf == -1) {
306            return false;
307        }
308        if (indexOf < 3) {
309            if (indexOf == 0) {
310                return true
311            } else if (indexOf == 1) {
312                return item.eventType == "AllocEvent"
313            } else if (indexOf == 2) {
314                return item.eventType == "MmapEvent"
315            }
316        } else if (indexOf - 3 < statisticsSelection.length) {
317            let selectionElement = statisticsSelection[indexOf - 3];
318            if (selectionElement.memoryTap != undefined && selectionElement.max != undefined) {
319                if (selectionElement.memoryTap.indexOf("Malloc") != -1) {
320                    return item.eventType == "AllocEvent" && item.heapSize == selectionElement.max
321                } else if(selectionElement.memoryTap.indexOf("Mmap") != -1){
322                    return item.eventType == "MmapEvent" && item.heapSize == selectionElement.max
323                } else {
324                    return item.subType == selectionElement.memoryTap && item.heapSize == selectionElement.max
325                }
326            }
327        }
328        return false;
329    }
330
331    public getByteWithUnit(bytes: number): string {
332        if (bytes < 0) {
333            return "-" + this.getByteWithUnit(Math.abs(bytes))
334        }
335        let currentBytes = bytes
336        let kb1 = 1 << 10;
337        let mb1 = 1 << 10 << 10;
338        let gb1 = 1 << 10 << 10 << 10; // 1 gb
339        let res = ""
340        if (currentBytes > gb1) {
341            res += (currentBytes / gb1).toFixed(2) + " Gb";
342        } else if (currentBytes > mb1) {
343            res += (currentBytes / mb1).toFixed(2) + " Mb";
344        } else if (currentBytes > kb1) {
345            res += (currentBytes / kb1).toFixed(2) + " Kb";
346        } else {
347            res += Math.round(currentBytes) + " byte";
348        }
349        return res
350    }
351
352    public getTimeString(ns: number): string {
353        let currentNs = ns
354        let hour1 = 3600_000_000_000
355        let minute1 = 60_000_000_000
356        let second1 = 1_000_000_000;
357        let millisecond1 = 1_000_000;
358        let microsecond1 = 1_000;
359        let res = "";
360        if (currentNs >= hour1) {
361            res += Math.floor(currentNs / hour1) + "h ";
362            currentNs = currentNs - Math.floor(currentNs / hour1) * hour1
363        }
364        if (currentNs >= minute1) {
365            res += Math.floor(currentNs / minute1) + "m ";
366            currentNs = currentNs - Math.floor(ns / minute1) * minute1
367        }
368        if (currentNs >= second1) {
369            res += Math.floor(currentNs / second1) + "s ";
370            currentNs = currentNs - Math.floor(currentNs / second1) * second1
371        }
372        if (currentNs >= millisecond1) {
373            res += Math.floor(currentNs / millisecond1) + "ms ";
374            currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1
375        }
376        if (currentNs >= microsecond1) {
377            res += Math.floor(currentNs / microsecond1) + "μs ";
378            currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1
379        }
380        if (currentNs > 0) {
381            res += currentNs + "ns ";
382        }
383        if (res == "") {
384            res = ns + "";
385        }
386        return res
387    }
388
389    clearAll() {
390        this.DATA_DICT.clear();
391        this.HEAP_FRAME_DATA = [];
392        this.HEAP_FRAME_STACK.clear();
393    }
394
395    listToTree(target: NativeHookCallInfo, src: NativeHookCallInfo) {
396        if (target.depth == src.depth + 1) {
397            target.pid = src.id;
398            src.children.push(target)
399        } else {
400            if (src.children.length > 0) {
401                this.listToTree(target, <NativeHookCallInfo>src.children[0]);
402            }
403        }
404    }
405}
406
407let nativeMemoryWorker = new TraceWorkerNativeMemory()
408
409class HeapTreeDataBean {
410    MoudleName: string | undefined
411    AllocationFunction: string | undefined
412    symbolId: number = 0
413    fileId: number = 0
414    startTs: number = 0
415    endTs: number = 0
416    eventType: string | undefined
417    depth: number = 0
418    heapSize: number = 0
419    eventId: string = ""
420}
421
422class NativeHookStatistics {
423    eventId: number = 0;
424    eventType: string = "";
425    subType: string = "";
426    subTypeId: number = 0;
427    heapSize: number = 0;
428    addr: string = "";
429    startTs: number = 0;
430    endTs: number = 0;
431    sumHeapSize: number = 0;
432    max: number = 0;
433    count: number = 0;
434    tid: number = 0;
435    threadName: string = "";
436    isSelected: boolean = false;
437}
438
439class NativeHookCallInfo extends ChartStruct {
440    id: string = "";
441    pid: string | undefined;
442    library: string = "";
443    symbolId: number = 0;
444    title: string = "";
445    count: number = 0;
446    countValue: string = ""
447    countPercent: string = "";
448    type: number = 0;
449    heapSize: number = 0;
450    heapPercent: string = "";
451    heapSizeStr: string = "";
452    eventId: number = 0;
453    threadId: number = 0;
454    threadName: string = "";
455    isSelected: boolean = false;
456}
457
458class NativeMemory {
459    index: number = 0;
460    eventId: number = 0;
461    eventType: string = "";
462    subType: string = "";
463    addr: string = "";
464    startTs: number = 0;
465    endTs: number = 0;
466    timestamp: string = ""
467    heapSize: number = 0;
468    heapSizeUnit: string = "";
469    symbol: string = "";
470    library: string = "";
471    isSelected: boolean = false;
472    state: string = "";
473    threadId:number = 0;
474    threadName:string = "";
475}
476
477class StatisticsSelection{
478    memoryTap:string = "";
479    max:number = 0;
480}