/* * Copyright (C) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { convertJSON, DataCache, getByteWithUnit, HeapTreeDataBean, LogicHandler, MerageBean, merageBeanDataSplit, postMessage, setFileName, } from './ProcedureLogicWorkerCommon.js'; export class ProcedureLogicWorkerNativeMemory extends LogicHandler { selectTotalSize = 0; selectTotalCount = 0; stackCount = 0; NATIVE_MEMORY_DATA: Array<NativeEvent> = []; currentTreeMapData: any = {}; currentTreeList: any[] = []; queryAllCallchainsSamples: NativeHookStatistics[] = []; currentSamples: NativeHookStatistics[] = []; allThreads: NativeHookCallInfo[] = []; splitMapData: any = {}; searchValue: string = ''; currentEventId: string = ''; chartComplete: Map<number, boolean> = new Map<number, boolean>(); realTimeDif: number = 0; responseTypes: { key: number; value: string }[] = []; totalNS: number = 0; isAnalysis: boolean = false; isStatistic: boolean = false; boxRangeNativeHook: Array<NativeMemory> = []; clearBoxSelectionData: boolean = false; nativeMemoryArgs?: Map<string, any> private dataCache = DataCache.getInstance(); handle(data: any): void { this.currentEventId = data.id; if (data && data.type) { switch (data.type) { case 'native-memory-init': this.clearAll(); if (data.params.isRealtime) { this.realTimeDif = data.params.realTimeDif; } this.dataCache.dataDict = data.params.dataDict; this.initNMChartData(); break; case 'native-memory-queryNMChartData': this.NATIVE_MEMORY_DATA = convertJSON(data.params.list) || []; this.initNMFrameData(); break; case 'native-memory-queryNMFrameData': let arr = convertJSON(data.params.list) || []; this.initNMStack(arr); arr = []; self.postMessage({ id: data.id, action: 'native-memory-init', results: [], }); break; case 'native-memory-queryCallchainsSamples': this.searchValue = ''; if (data.params.list) { let callchainsSamples = convertJSON(data.params.list) || []; this.queryAllCallchainsSamples = callchainsSamples; this.freshCurrentCallchains(callchainsSamples, true); // @ts-ignore self.postMessage({ id: data.id, action: data.action, results: this.allThreads, }); } else { this.queryCallchainsSamples( 'native-memory-queryCallchainsSamples', data.params.leftNs, data.params.rightNs, data.params.types ); } break; case 'native-memory-queryStatisticCallchainsSamples': this.searchValue = ''; if (data.params.list) { let samples = convertJSON(data.params.list) || []; this.queryAllCallchainsSamples = samples; this.freshCurrentCallchains(samples, true); // @ts-ignore self.postMessage({ id: data.id, action: data.action, results: this.allThreads, }); } else { this.queryStatisticCallchainsSamples( 'native-memory-queryStatisticCallchainsSamples', data.params.leftNs, data.params.rightNs, data.params.types ); } break; case 'native-memory-queryAnalysis': if (data.params.list) { let samples = convertJSON(data.params.list) || []; this.queryAllCallchainsSamples = samples; self.postMessage({ id: data.id, action: data.action, results: this.combineStatisticAndCallChain(samples), }); } else { if (data.params.isStatistic) { this.isStatistic = true; this.queryStatisticCallchainsSamples( 'native-memory-queryAnalysis', data.params.leftNs, data.params.rightNs, data.params.types ); } else { this.isStatistic = false; this.queryCallchainsSamples( 'native-memory-queryAnalysis', data.params.leftNs, data.params.rightNs, data.params.types ); } } break; case 'native-memory-queryNativeHookEvent': if (data.params) { if (data.params.list) { this.boxRangeNativeHook = convertJSON(data.params.list); if (this.nativeMemoryArgs?.get('refresh')) { this.clearBoxSelectionData = this.boxRangeNativeHook.length > 100_0000; } this.supplementNativeHoodData(); postMessage(data.id, data.action, this.resolvingActionNativeMemory(this.nativeMemoryArgs!), 100_0000); if (this.clearBoxSelectionData) { this.boxRangeNativeHook = []; } } else if (data.params.get('refresh') || this.boxRangeNativeHook.length === 0) { this.nativeMemoryArgs = data.params; let leftNs = data.params.get('leftNs'); let rightNs = data.params.get('rightNs'); let types = data.params.get('types'); this.boxRangeNativeHook = []; this.queryNativeHookEvent(leftNs, rightNs, types); } else { this.nativeMemoryArgs = data.params; postMessage(data.id, data.action, this.resolvingActionNativeMemory(this.nativeMemoryArgs!), 100_0000); if (this.clearBoxSelectionData) { this.boxRangeNativeHook = []; } } } break; case 'native-memory-action': if (data.params) { self.postMessage({ id: data.id, action: data.action, results: this.resolvingAction(data.params), }); } break; case 'native-memory-chart-action': if (data.params) { postMessage(data.id, data.action, this.resolvingActionNativeMemoryChartData(data.params)); } break; case 'native-memory-calltree-action': if (data.params) { self.postMessage({ id: data.id, action: data.action, results: this.resolvingNMCallAction(data.params), }); } break; case 'native-memory-init-responseType': this.initResponseTypeList(data.params); self.postMessage({ id: data.id, action: data.action, results: [], }); break; case 'native-memory-get-responseType': self.postMessage({ id: data.id, action: data.action, results: this.responseTypes, }); break; case 'native-memory-queryNativeHookStatistic': if (data.params.list) { let arr = this.statisticDataHandler(convertJSON(data.params.list)); postMessage(data.id, data.action, this.handleNativeHookStatisticData(arr)); } else { this.totalNS = data.params.totalNS; this.queryNativeHookStatistic(data.params.type); } break; } } } initNMChartData() { this.queryData( this.currentEventId, 'native-memory-queryNMChartData', ` select * from ( select h.start_ts - t.start_ts as startTime, h.heap_size as heapSize, (case when h.event_type = 'AllocEvent' then 0 else 1 end) as eventType from native_hook h ,trace_range t where h.start_ts between t.start_ts and t.end_ts and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent') union all select h.end_ts - t.start_ts as startTime, h.heap_size as heapSize, (case when h.event_type = 'AllocEvent' then 2 else 3 end) as eventType from native_hook h ,trace_range t where h.start_ts between t.start_ts and t.end_ts and h.end_ts between t.start_ts and t.end_ts and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent') ) order by startTime; `, {} ); } queryNativeHookStatistic(type: number) { let condition: string; if (type === 0) { condition = 'and type = 0'; } else if (type === 1) { condition = 'and type > 0'; } else { condition = ''; } let sql = ` select callchain_id callchainId, ts - start_ts as ts, apply_count applyCount, apply_size applySize, release_count releaseCount, release_size releaseSize from native_hook_statistic,trace_range where ts between start_ts and end_ts ${condition}; `; this.queryData(this.currentEventId, 'native-memory-queryNativeHookStatistic', sql, {}); } queryNativeHookEvent(leftNs: number, rightNs: number, types: Array<string>) { let condition = types.length === 1 ? `and A.event_type = ${types[0]}` : `and (A.event_type = 'AllocEvent' or A.event_type = 'MmapEvent')`; let libId = this.nativeMemoryArgs?.get('filterResponseType'); let allocType = this.nativeMemoryArgs?.get('filterAllocType'); let eventType = this.nativeMemoryArgs?.get('filterEventType'); if (libId !== undefined && libId !== -1) { condition = `${condition} and last_lib_id = ${libId}`;// filter lib } if (eventType === '1') { condition = `${condition} and event_type = 'AllocEvent'`; } if (eventType === '2') { condition = `${condition} and event_type = 'MmapEvent'`; } if (allocType === '1') { condition = `${condition} and ((A.end_ts - B.start_ts) > ${rightNs} or A.end_ts is null)`; } if (allocType === '2') { condition = `${condition} and (A.end_ts - B.start_ts) <= ${rightNs}`; } let sql = ` select callchain_id as eventId, event_type as eventType, heap_size as heapSize, ('0x' || addr) as addr, (A.start_ts - B.start_ts) as startTs, (A.end_ts - B.start_ts) as endTs, tid as threadId, sub_type_id as subTypeId, ifnull(last_lib_id,0) as lastLibId from native_hook A, trace_range B left join thread t on A.itid = t.id where A.start_ts - B.start_ts between ${leftNs} and ${rightNs} ${condition} `; this.queryData(this.currentEventId, 'native-memory-queryNativeHookEvent', sql, {}); } supplementNativeHoodData() { let len = this.boxRangeNativeHook.length; for(let i = 0, j = len -1; i <= j; i++, j--){ this.fillNativeHook(this.boxRangeNativeHook[i], i); if (i !== j) { this.fillNativeHook(this.boxRangeNativeHook[j], j); } } } fillNativeHook(memory: NativeMemory, index: number) { if (memory.subTypeId !== null && memory.subType === undefined) { memory.subType = this.dataCache.dataDict.get(memory.subTypeId) || '-'; } memory.index = index; let arr = this.dataCache.nmHeapFrameMap.get(memory.eventId) || []; let frame = Array.from(arr) .reverse() .find((item) => { let fileName = this.dataCache.dataDict.get(item.fileId); return !((fileName ?? '').includes('libc++') || (fileName ?? '').includes('musl')); }); if (frame === null || frame === undefined) { if (arr.length > 0) { frame = arr[0]; } } if (frame !== null && frame !== undefined) { memory.symbol = this.groupCutFilePath(frame.symbolId, this.dataCache.dataDict.get(frame.symbolId) || ''); memory.library = this.groupCutFilePath( frame.fileId, this.dataCache.dataDict.get(frame.fileId) || 'Unknown Path' ); } else { memory.symbol = '-'; memory.library = '-'; } } statisticDataHandler(arr: Array<any>) { let callGroupMap: Map<number, any[]> = new Map<number, any[]>(); let obj = {}; for (let hook of arr) { if ((obj as any)[hook.ts]) { let data = (obj as any)[hook.ts] as any; data.startTime = hook.ts; data.dur = 0; if (callGroupMap.has(hook.callchainId)) { let calls = callGroupMap.get(hook.callchainId); let last = calls![calls!.length - 1]; data.heapsize += hook.applySize - last.applySize - (hook.releaseSize - last.releaseSize); data.density += hook.applyCount - last.applyCount - (hook.releaseCount - last.releaseCount); calls!.push(hook); } else { data.heapsize += hook.applySize - hook.releaseSize; data.density += hook.applyCount - hook.releaseCount; callGroupMap.set(hook.callchainId, [hook]); } } else { let data: any = {}; data.startTime = hook.ts; data.dur = 0; if (callGroupMap.has(hook.callchainId)) { let calls = callGroupMap.get(hook.callchainId); let last = calls![calls!.length - 1]; data.heapsize = hook.applySize - last.applySize - (hook.releaseSize - last.releaseSize); data.density = hook.applyCount - last.applyCount - (hook.releaseCount - last.releaseCount); calls!.push(hook); } else { data.heapsize = hook.applySize - hook.releaseSize; data.density = hook.applyCount - hook.releaseCount; callGroupMap.set(hook.callchainId, [hook]); } (obj as any)[hook.ts] = data; } } return Object.values(obj) as { startTime: number; heapsize: number; density: number; dur: number; }[]; } handleNativeHookStatisticData( arr: { startTime: number; heapsize: number; density: number; dur: number; }[] ) { let maxSize = 0, maxDensity = 0, minSize = 0, minDensity = 0; for (let i = 0, len = arr.length; i < len; i++) { if (i == len - 1) { arr[i].dur = this.totalNS - arr[i].startTime; } else { arr[i + 1].heapsize = arr[i].heapsize + arr[i + 1].heapsize; arr[i + 1].density = arr[i].density + arr[i + 1].density; arr[i].dur = arr[i + 1].startTime - arr[i].startTime; } maxSize = Math.max(maxSize, arr[i].heapsize); maxDensity = Math.max(maxDensity, arr[i].density); minSize = Math.min(minSize, arr[i].heapsize); minDensity = Math.min(minDensity, arr[i].density); } return arr.map((it) => { (it as any).maxHeapSize = maxSize; (it as any).maxDensity = maxDensity; (it as any).minHeapSize = minSize; (it as any).minDensity = minDensity; return it; }); } initResponseTypeList(list: any[]) { this.responseTypes = [ { key: -1, value: 'ALL', }, ]; list.forEach((item) => { if (item.lastLibId == null) { this.responseTypes.push({ key: 0, value: '-', }); } else { this.responseTypes.push({ key: item.lastLibId, value: this.groupCutFilePath(item.lastLibId, item.value) || '-', }); } }); } initNMFrameData() { this.queryData( this.currentEventId, 'native-memory-queryNMFrameData', ` select h.symbol_id as symbolId, h.file_id as fileId, h.depth, h.callchain_id as eventId, h.vaddr as addr from native_hook_frame h `, {} ); } initNMStack(frameArr: Array<HeapTreeDataBean>) { frameArr.map((frame) => { let frameEventId = frame.eventId; if (this.dataCache.nmHeapFrameMap.has(frameEventId)) { this.dataCache.nmHeapFrameMap.get(frameEventId)!.push(frame); } else { this.dataCache.nmHeapFrameMap.set(frameEventId, [frame]); } }); } resolvingAction(paramMap: Map<string, any>): Array<NativeHookCallInfo | NativeMemory | HeapStruct> { let actionType = paramMap.get('actionType'); if (actionType === 'memory-stack') { return this.resolvingActionNativeMemoryStack(paramMap); } else if (actionType === 'native-memory-state-change') { let startTs = paramMap.get('startTs'); let currentSelection = this.boxRangeNativeHook.filter((item) => { return item.startTs === startTs; }); if (currentSelection.length > 0) { currentSelection[0].isSelected = true; } return []; } else { return []; } } resolvingActionNativeMemoryChartData(paramMap: Map<string, any>): Array<HeapStruct> { let nativeMemoryType: number = paramMap.get('nativeMemoryType') as number; let totalNS: number = paramMap.get('totalNS') as number; let arr: Array<HeapStruct> = []; let nmMaxSize = 0; let nmMaxDensity = 0; let nmMinSize = 0; let nmMinDensity = 0; let nmTempSize = 0; let nmTempDensity = 0; let nmFilterLen = 0; let nmFilterLevel = 0; let putArr = (ne: NativeEvent, filterLevel: number, finish: boolean) => { let nmHeapStruct = new HeapStruct(); nmHeapStruct.startTime = ne.startTime; if (arr.length == 0) { if (ne.eventType == 0 || ne.eventType == 1) { nmHeapStruct.density = 1; nmHeapStruct.heapsize = ne.heapSize; } else { nmHeapStruct.density = -1; nmHeapStruct.heapsize = 0 - ne.heapSize; } nmMaxSize = nmHeapStruct.heapsize; nmMaxDensity = nmHeapStruct.density; nmMinSize = nmHeapStruct.heapsize; nmMinDensity = nmHeapStruct.density; arr.push(nmHeapStruct); } else { let last = arr[arr.length - 1]; last.dur = nmHeapStruct.startTime! - last.startTime!; if (last.dur > filterLevel || finish) { if (ne.eventType == 0 || ne.eventType == 1) { nmHeapStruct.density = last.density! + nmTempDensity + 1; nmHeapStruct.heapsize = last.heapsize! + nmTempSize + ne.heapSize; } else { nmHeapStruct.density = last.density! + nmTempDensity - 1; nmHeapStruct.heapsize = last.heapsize! + nmTempSize - ne.heapSize; } nmTempDensity = 0; nmTempSize = 0; if (nmHeapStruct.density > nmMaxDensity) { nmMaxDensity = nmHeapStruct.density; } if (nmHeapStruct.density < nmMinDensity) { nmMinDensity = nmHeapStruct.density; } if (nmHeapStruct.heapsize > nmMaxSize) { nmMaxSize = nmHeapStruct.heapsize; } if (nmHeapStruct.heapsize < nmMinSize) { nmMinSize = nmHeapStruct.heapsize; } arr.push(nmHeapStruct); } else { if (ne.eventType == 0 || ne.eventType == 1) { nmTempDensity = nmTempDensity + 1; nmTempSize = nmTempSize + ne.heapSize; } else { nmTempDensity = nmTempDensity - 1; nmTempSize = nmTempSize - ne.heapSize; } } } }; if (nativeMemoryType == 1) { let temp = this.NATIVE_MEMORY_DATA.filter((ne) => ne.eventType === 0 || ne.eventType === 2); nmFilterLen = temp.length; nmFilterLevel = this.getFilterLevel(nmFilterLen); temp.map((ne, index) => putArr(ne, nmFilterLevel, index === nmFilterLen - 1)); temp.length = 0; } else if (nativeMemoryType == 2) { let temp = this.NATIVE_MEMORY_DATA.filter((ne) => ne.eventType === 1 || ne.eventType === 3); nmFilterLen = temp.length; nmFilterLevel = this.getFilterLevel(nmFilterLen); temp.map((ne, index) => putArr(ne, nmFilterLevel, index === nmFilterLen - 1)); temp.length = 0; } else { nmFilterLen = this.NATIVE_MEMORY_DATA.length; let filterLevel = this.getFilterLevel(nmFilterLen); this.NATIVE_MEMORY_DATA.map((ne, index) => putArr(ne, filterLevel, index === nmFilterLen - 1)); } if (arr.length > 0) { arr[arr.length - 1].dur = totalNS - arr[arr.length - 1].startTime!; } arr.map((heapStruct) => { heapStruct.maxHeapSize = nmMaxSize; heapStruct.maxDensity = nmMaxDensity; heapStruct.minHeapSize = nmMinSize; heapStruct.minDensity = nmMinDensity; }); this.chartComplete.set(nativeMemoryType, true); if (this.chartComplete.has(0) && this.chartComplete.has(1) && this.chartComplete.has(2)) { this.NATIVE_MEMORY_DATA = []; } return arr; } resolvingActionNativeMemoryStack(paramMap: Map<string, any>) { let eventId = paramMap.get('eventId'); let frameArr = this.dataCache.nmHeapFrameMap.get(eventId) || []; let arr: Array<NativeHookCallInfo> = []; frameArr.map((frame) => { let target = new NativeHookCallInfo(); target.eventId = frame.eventId; target.depth = frame.depth; target.addr = frame.addr; target.symbol = this.groupCutFilePath(frame.symbolId, this.dataCache.dataDict.get(frame.symbolId) || '') ?? ''; target.library = this.groupCutFilePath(frame.fileId, this.dataCache.dataDict.get(frame.fileId) || '') ?? ''; target.title = `[ ${target.symbol} ] ${target.library}`; target.type = target.library.endsWith('.so.1') || target.library.endsWith('.dll') || target.library.endsWith('.so') ? 0 : 1; arr.push(target); }); return arr; } resolvingActionNativeMemory(paramMap: Map<string, any>): Array<NativeMemory> { let filterAllocType = paramMap.get('filterAllocType'); let filterEventType = paramMap.get('filterEventType'); let filterResponseType = paramMap.get('filterResponseType'); let leftNs = paramMap.get('leftNs'); let rightNs = paramMap.get('rightNs'); let sortColumn = paramMap.get('sortColumn'); let sortType = paramMap.get('sortType'); let statisticsSelection = paramMap.get('statisticsSelection'); let filter = this.boxRangeNativeHook; if ((filterAllocType !== undefined && filterAllocType !== 0) || (filterEventType !== undefined && filterEventType !== 0) || (filterResponseType !== undefined && filterResponseType !== -1) ) { filter = this.boxRangeNativeHook.filter((item) => { let filterAllocation = true; let freed = item.endTs > leftNs && item.endTs <= rightNs && item.endTs !== 0 && item.endTs !== null; if (filterAllocType === '1') { filterAllocation = !freed; } else if (filterAllocType == '2') { filterAllocation = freed; } let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection); let filterLastLib = filterResponseType == -1 ? true : filterResponseType == item.lastLibId; return filterAllocation && filterNative && filterLastLib; }); } if (sortColumn !== undefined && sortType !== undefined && sortColumn !== '' && sortType !== 0) { return this.sortByNativeMemoryColumn(sortColumn, sortType, filter); } else { return filter; } } sortByNativeMemoryColumn(nmMemoryColumn: string, nmMemorySort: number, list: Array<NativeMemory>) { if (nmMemorySort === 0) { return list; } else { return list.sort((memoryLeftData: any, memoryRightData: any) => { if (nmMemoryColumn === 'index' || nmMemoryColumn === 'startTs' || nmMemoryColumn === 'heapSize') { return nmMemorySort == 1 ? memoryLeftData[nmMemoryColumn] - memoryRightData[nmMemoryColumn] : memoryRightData[nmMemoryColumn] - memoryLeftData[nmMemoryColumn]; } else { if (nmMemorySort == 1) { if (memoryLeftData[nmMemoryColumn] > memoryRightData[nmMemoryColumn]) { return 1; } else if (memoryLeftData[nmMemoryColumn] === memoryRightData[nmMemoryColumn]) { return 0; } else { return -1; } } else { if (memoryRightData[nmMemoryColumn] > memoryLeftData[nmMemoryColumn]) { return 1; } else if (memoryLeftData[nmMemoryColumn] == memoryRightData[nmMemoryColumn]) { return 0; } else { return -1; } } } }); } } groupCutFilePath(fileId: number, path: string): string { let name: string; if (this.dataCache.nmFileDict.has(fileId)) { name = this.dataCache.nmFileDict.get(fileId) ?? ''; } else { let currentPath = path.substring(path.lastIndexOf('/') + 1); this.dataCache.nmFileDict.set(fileId, currentPath); name = currentPath; } return name == '' ? '-' : name; } traverseSampleTree(stack: NativeHookCallInfo, hook: NativeHookStatistics) { stack.count += 1; stack.countValue = `${stack.count}`; stack.countPercent = `${((stack.count / this.selectTotalCount) * 100).toFixed(1)}%`; stack.size += hook.heapSize; stack.tid = hook.tid; stack.threadName = hook.threadName; stack.heapSizeStr = `${getByteWithUnit(stack.size)}`; stack.heapPercent = `${((stack.size / this.selectTotalSize) * 100).toFixed(1)}%`; if (stack.children.length > 0) { stack.children.map((child) => { this.traverseSampleTree(child as NativeHookCallInfo, hook); }); } } traverseTree(stack: NativeHookCallInfo, hook: NativeHookStatistics) { stack.count = 1; stack.countValue = `${stack.count}`; stack.countPercent = `${((stack!.count / this.selectTotalCount) * 100).toFixed(1)}%`; stack.size = hook.heapSize; stack.tid = hook.tid; stack.threadName = hook.threadName; stack.heapSizeStr = `${getByteWithUnit(stack!.size)}`; stack.heapPercent = `${((stack!.size / this.selectTotalSize) * 100).toFixed(1)}%`; if (stack.children.length > 0) { stack.children.map((child) => { this.traverseTree(child as NativeHookCallInfo, hook); }); } } getTypeFromIndex( indexOf: number, item: NativeHookStatistics | NativeMemory, statisticsSelection: Array<StatisticsSelection> ): boolean { if (indexOf == -1) { return false; } if (indexOf < 3) { if (indexOf == 0) { return true; } else if (indexOf == 1) { return item.eventType == 'AllocEvent'; } else if (indexOf == 2) { return item.eventType == 'MmapEvent'; } } else if (indexOf - 3 < statisticsSelection.length) { let selectionElement = statisticsSelection[indexOf - 3]; if (selectionElement.memoryTap != undefined && selectionElement.max != undefined) { if (selectionElement.memoryTap.indexOf('Malloc') != -1) { return item.eventType == 'AllocEvent' && item.heapSize == selectionElement.max; } else if (selectionElement.memoryTap.indexOf('Mmap') != -1) { return item.eventType == 'MmapEvent' && item.heapSize == selectionElement.max && item.subTypeId === null; } else { return item.subType == selectionElement.memoryTap; } } if (selectionElement.max === undefined && typeof selectionElement.memoryTap === 'number') { return item.subTypeId === selectionElement.memoryTap; } } return false; } clearAll() { this.dataCache.clearNM(); this.splitMapData = {}; this.currentSamples = []; this.allThreads = []; this.queryAllCallchainsSamples = []; this.NATIVE_MEMORY_DATA = []; this.chartComplete.clear(); this.realTimeDif = 0; this.currentTreeMapData = {}; this.currentTreeList.length = 0; this.responseTypes.length = 0; this.boxRangeNativeHook = []; this.nativeMemoryArgs?.clear(); } queryCallchainsSamples(action: string, leftNs: number, rightNs: number, types: Array<string>) { this.queryData( this.currentEventId, action, `select A.id, callchain_id as eventId, event_type as eventType, heap_size as heapSize, (A.start_ts - B.start_ts) as startTs, (A.end_ts - B.start_ts) as endTs, tid, ifnull(last_lib_id,0) as lastLibId, t.name as threadName, A.addr, A.sub_type_id as subTypeId from native_hook A, trace_range B left join thread t on A.itid = t.id where A.start_ts - B.start_ts between ${leftNs} and ${rightNs} and A.event_type in (${types.join(',')}) `, {} ); } queryStatisticCallchainsSamples(action: string, leftNs: number, rightNs: number, types: Array<number>) { let condition = ''; if (types.length === 1) { if (types[0] === 0) { condition = 'and type = 0'; } else { condition = 'and type != 0'; } } this.queryData( this.currentEventId, action, `select A.id, 0 as tid, callchain_id as eventId, (case when type = 0 then 'AllocEvent' else 'MmapEvent' end) as eventType, (case when sub_type_id not null then sub_type_id else type end) as subTypeId, apply_size as heapSize, release_size as freeSize, apply_count as count, release_count as freeCount, (max(A.ts) - B.start_ts) as startTs from native_hook_statistic A, trace_range B where A.ts - B.start_ts between ${leftNs} and ${rightNs} ${condition} group by callchain_id; `, {} ); } combineStatisticAndCallChain(samples: NativeHookStatistics[]) { samples.sort((a, b) => a.id - b.id); const analysisSampleList = new Array<AnalysisSample>(); const applyAllocSamples = new Array<AnalysisSample>(); const applyMmapSamples = new Array<AnalysisSample>(); for (const sample of samples) { const count = this.isStatistic ? sample.count : 1; const analysisSample = new AnalysisSample(sample.id, sample.heapSize, count, sample.eventType, sample.startTs); if (this.isStatistic) { analysisSample.releaseCount = sample.freeCount; analysisSample.releaseSize = sample.freeSize; switch (sample.subTypeId) { case 1: analysisSample.subType = 'MmapEvent'; break; case 2: analysisSample.subType = 'FILE_PAGE_MSG'; break; case 3: analysisSample.subType = 'MEMORY_USING_MSG'; break; default: analysisSample.subType = this.dataCache.dataDict.get(sample.subTypeId); } } else { let subType = undefined; if (sample.subTypeId) { subType = this.dataCache.dataDict.get(sample.subTypeId); } analysisSample.endTs = sample.endTs; analysisSample.addr = sample.addr; analysisSample.tid = sample.tid; analysisSample.subType = subType; } if (['FreeEvent', 'MunmapEvent'].includes(sample.eventType)) { if (sample.eventType === 'FreeEvent') { this.setApplyIsRelease(analysisSample, applyAllocSamples); } else { this.setApplyIsRelease(analysisSample, applyMmapSamples); } continue; } else { if (sample.eventType === 'AllocEvent') { applyAllocSamples.push(analysisSample); } else { applyMmapSamples.push(analysisSample); } } const callChains = this.dataCache.nmHeapFrameMap.get(sample.eventId) || []; if (!callChains || callChains.length === 0) { return; } let index = callChains.length - 1; let lastFilterCallChain: HeapTreeDataBean | undefined | null; while (true) { // if all call stack is musl or libc++. use stack top lib if (index < 0) { lastFilterCallChain = callChains[callChains.length - 1]; break; } lastFilterCallChain = callChains[index]; const libPath = this.dataCache.dataDict.get(lastFilterCallChain.fileId); //ignore musl and libc++ so if (libPath?.includes('musl') || libPath?.includes('libc++')) { index--; } else { lastFilterCallChain = lastFilterCallChain; break; } } const filePath = this.dataCache.dataDict.get(lastFilterCallChain.fileId)!; let libName = ''; if (filePath) { const path = filePath.split('/'); libName = path[path.length - 1]; } const symbolName = this.dataCache.dataDict.get(lastFilterCallChain.symbolId) || libName + ' (' + sample.addr + ')'; analysisSample.libId = lastFilterCallChain.fileId; analysisSample.libName = libName; analysisSample.symbolId = lastFilterCallChain.symbolId; analysisSample.symbolName = symbolName; analysisSampleList.push(analysisSample); } return analysisSampleList; } setApplyIsRelease(sample: AnalysisSample, arr: Array<AnalysisSample>) { let idx = arr.length - 1; for (idx; idx >= 0; idx--) { let item = arr[idx]; if (item.endTs === sample.startTs && item.addr === sample.addr) { arr.splice(idx, 1); item.isRelease = true; return; } } } freshCurrentCallchains(samples: NativeHookStatistics[], isTopDown: boolean) { this.currentTreeMapData = {}; this.currentTreeList = []; let totalSize = 0; let totalCount = 0; samples.forEach((nativeHookSample) => { if (nativeHookSample.eventId == -1) { return; } totalSize += nativeHookSample.heapSize; totalCount += nativeHookSample.count || 1; let callChains = this.createThreadSample(nativeHookSample); let topIndex = isTopDown ? 0 : callChains.length - 1; if (callChains.length > 0) { let root = this.currentTreeMapData[ nativeHookSample.tid + '-' + (callChains[topIndex].symbolId || '') + '-' + (callChains[topIndex].fileId || '') ]; if (root == undefined) { root = new NativeHookCallInfo(); root.threadName = nativeHookSample.threadName; this.currentTreeMapData[ nativeHookSample.tid + '-' + (callChains[topIndex].symbolId || '') + '-' + (callChains[topIndex].fileId || '') ] = root; this.currentTreeList.push(root); } NativeHookCallInfo.merageCallChainSample(root, callChains[topIndex], nativeHookSample); if (callChains.length > 1) { this.merageChildrenByIndex(root, callChains, topIndex, nativeHookSample, isTopDown); } } }); let rootMerageMap: any = {}; // @ts-ignore let threads = Object.values(this.currentTreeMapData); threads.forEach((merageData: any) => { if (rootMerageMap[merageData.tid] == undefined) { let threadMerageData = new NativeHookCallInfo(); //æ–°å¢žè¿›ç¨‹çš„èŠ‚ç‚¹æ•°æ® threadMerageData.canCharge = false; threadMerageData.type = -1; threadMerageData.symbolName = `${merageData.threadName || 'Thread'} [${merageData.tid}]`; threadMerageData.symbol = threadMerageData.symbolName; threadMerageData.children.push(merageData); threadMerageData.initChildren.push(merageData); threadMerageData.count = merageData.count || 1; threadMerageData.heapSize = merageData.heapSize; threadMerageData.totalCount = totalCount; threadMerageData.totalSize = totalSize; rootMerageMap[merageData.tid] = threadMerageData; } else { rootMerageMap[merageData.tid].children.push(merageData); rootMerageMap[merageData.tid].initChildren.push(merageData); rootMerageMap[merageData.tid].count += merageData.count || 1; rootMerageMap[merageData.tid].heapSize += merageData.heapSize; rootMerageMap[merageData.tid].totalCount = totalCount; rootMerageMap[merageData.tid].totalSize = totalSize; } merageData.parentNode = rootMerageMap[merageData.tid]; //åèŠ‚ç‚¹æ·»åŠ çˆ¶èŠ‚ç‚¹çš„å¼•ç”¨ }); let id = 0; this.currentTreeList.forEach((nmTreeNode) => { nmTreeNode.totalCount = totalCount; nmTreeNode.totalSize = totalSize; this.setMerageName(nmTreeNode); if (nmTreeNode.id == '') { nmTreeNode.id = id + ''; id++; } if (nmTreeNode.parentNode) { if (nmTreeNode.parentNode.id == '') { nmTreeNode.parentNode.id = id + ''; id++; } nmTreeNode.parentId = nmTreeNode.parentNode.id; } }); // @ts-ignore this.allThreads = Object.values(rootMerageMap) as NativeHookCallInfo[]; } groupCallchainSample(paramMap: Map<string, any>) { let groupMap: any = {}; let filterAllocType = paramMap.get('filterAllocType'); let filterEventType = paramMap.get('filterEventType'); let filterResponseType = paramMap.get('filterResponseType'); let leftNs = paramMap.get('leftNs'); let rightNs = paramMap.get('rightNs'); let nativeHookType = paramMap.get('nativeHookType'); let statisticsSelection = paramMap.get('statisticsSelection'); if (filterAllocType == '0' && filterEventType == '0' && filterResponseType == -1) { this.currentSamples = this.queryAllCallchainsSamples; return; } let filter = this.queryAllCallchainsSamples.filter((item) => { let filterAllocation = true; if (nativeHookType === 'native-hook') { if (filterAllocType == '1') { filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs && (item.endTs > rightNs || item.endTs == 0 || item.endTs == null); } else if (filterAllocType == '2') { filterAllocation = item.startTs >= leftNs && item.startTs <= rightNs && item.endTs <= rightNs && item.endTs != 0 && item.endTs != null; } } else { if (filterAllocType == '1') { filterAllocation = item.heapSize > item.freeSize; } else if (filterAllocType == '2') { filterAllocation = item.heapSize === item.freeSize; } } let filterLastLib = filterResponseType == -1 ? true : filterResponseType == item.lastLibId; let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection); return filterAllocation && filterNative && filterLastLib; }); filter.forEach((sample) => { let currentNode = groupMap[sample.tid + '-' + sample.eventId] || new NativeHookStatistics(); if (currentNode.count == 0) { Object.assign(currentNode, sample); if (filterAllocType == '1' && nativeHookType !== 'native-hook') { currentNode.heapSize = sample.heapSize - sample.freeSize; currentNode.count = sample.count - sample.freeCount; } if (currentNode.count === 0) { currentNode.count++; } } else { currentNode.count++; currentNode.heapSize += sample.heapSize; } groupMap[sample.tid + '-' + sample.eventId] = currentNode; }); // @ts-ignore this.currentSamples = Object.values(groupMap); } createThreadSample(sample: NativeHookStatistics) { return this.dataCache.nmHeapFrameMap.get(sample.eventId) || []; } merageChildrenByIndex( currentNode: NativeHookCallInfo, callChainDataList: any[], index: number, sample: NativeHookStatistics, isTopDown: boolean ) { isTopDown ? index++ : index--; let isEnd = isTopDown ? callChainDataList.length == index + 1 : index == 0; let node; if ( currentNode.initChildren.filter((child: any) => { if (child.symbolId == callChainDataList[index]?.symbolId && child.fileId == callChainDataList[index]?.fileId) { node = child; NativeHookCallInfo.merageCallChainSample(child, callChainDataList[index], sample); return true; } return false; }).length == 0 ) { node = new NativeHookCallInfo(); NativeHookCallInfo.merageCallChainSample(node, callChainDataList[index], sample); currentNode.children.push(node); currentNode.initChildren.push(node); this.currentTreeList.push(node); node.parentNode = currentNode; } if (node && !isEnd) this.merageChildrenByIndex(node, callChainDataList, index, sample, isTopDown); } setMerageName(currentNode: NativeHookCallInfo) { currentNode.symbol = this.groupCutFilePath(currentNode.symbolId, this.dataCache.dataDict.get(currentNode.symbolId) || '') ?? 'unknown'; currentNode.path = this.dataCache.dataDict.get(currentNode.fileId) || 'unknown'; currentNode.libName = setFileName(currentNode.path); currentNode.lib = currentNode.path; currentNode.symbolName = `[${currentNode.symbol}] ${currentNode.libName}`; currentNode.type = currentNode.libName.endsWith('.so.1') || currentNode.libName.endsWith('.dll') || currentNode.libName.endsWith('.so') ? 0 : 1; } clearSplitMapData(symbolName: string) { delete this.splitMapData[symbolName]; } resolvingNMCallAction(params: any[]) { if (params.length > 0) { params.forEach((item) => { if (item.funcName && item.funcArgs) { switch (item.funcName) { case 'groupCallchainSample': this.groupCallchainSample(item.funcArgs[0] as Map<string, any>); break; case 'getCallChainsBySampleIds': this.freshCurrentCallchains(this.currentSamples, item.funcArgs[0]); break; case 'hideSystemLibrary': merageBeanDataSplit.hideSystemLibrary(this.allThreads, this.splitMapData); break; case 'hideNumMaxAndMin': merageBeanDataSplit.hideNumMaxAndMin( this.allThreads, this.splitMapData, item.funcArgs[0], item.funcArgs[1] ); break; case 'splitAllProcess': merageBeanDataSplit.splitAllProcess(this.allThreads, this.splitMapData, item.funcArgs[0]); break; case 'resetAllNode': merageBeanDataSplit.resetAllNode(this.allThreads, this.currentTreeList, this.searchValue); break; case 'resotreAllNode': merageBeanDataSplit.resotreAllNode(this.splitMapData, item.funcArgs[0]); break; case 'splitTree': merageBeanDataSplit.splitTree( this.splitMapData, this.allThreads, item.funcArgs[0], item.funcArgs[1], item.funcArgs[2], this.currentTreeList, this.searchValue ); break; case 'setSearchValue': this.searchValue = item.funcArgs[0]; break; case 'clearSplitMapData': this.clearSplitMapData(item.funcArgs[0]); break; } } }); } return this.allThreads.filter((thread) => { return thread.children && thread.children.length > 0; }); } getFilterLevel(len: number): number { if (len > 300_0000) { return 50_0000; } else if (len > 200_0000) { return 30_0000; } else if (len > 100_0000) { return 10_0000; } else if (len > 50_0000) { return 5_0000; } else if (len > 30_0000) { return 2_0000; } else if (len > 15_0000) { return 1_0000; } else { return 0; } } } export class NativeHookStatistics { id: number = 0; eventId: number = 0; eventType: string = ''; subType: string = ''; subTypeId: number = 0; heapSize: number = 0; freeSize: number = 0; addr: string = ''; startTs: number = 0; endTs: number = 0; sumHeapSize: number = 0; max: number = 0; count: number = 0; freeCount: number = 0; tid: number = 0; threadName: string = ''; lastLibId: number = 0; isSelected: boolean = false; } export class NativeHookCallInfo extends MerageBean { #totalCount: number = 0; #totalSize: number = 0; library: string = ''; symbolId: number = 0; fileId: number = 0; title: string = ''; count: number = 0; countValue: string = ''; countPercent: string = ''; type: number = 0; heapSize: number = 0; heapPercent: string = ''; heapSizeStr: string = ''; eventId: number = 0; tid: number = 0; threadName: string = ''; eventType: string = ''; isSelected: boolean = false; set totalCount(total: number) { this.#totalCount = total; this.countValue = this.count + ''; this.size = this.heapSize; this.countPercent = `${((this.count / total) * 100).toFixed(1)}%`; } get totalCount() { return this.#totalCount; } set totalSize(total: number) { this.#totalSize = total; this.heapSizeStr = `${getByteWithUnit(this.heapSize)}`; this.heapPercent = `${((this.heapSize / total) * 100).toFixed(1)}%`; } get totalSize() { return this.#totalSize; } static merageCallChainSample( currentNode: NativeHookCallInfo, callChain: HeapTreeDataBean, sample: NativeHookStatistics ) { if (currentNode.symbol == undefined || currentNode.symbol == '') { currentNode.symbol = callChain.AllocationFunction || ''; currentNode.addr = callChain.addr; currentNode.eventId = sample.eventId; currentNode.eventType = sample.eventType; currentNode.symbolId = callChain.symbolId; currentNode.fileId = callChain.fileId; currentNode.tid = sample.tid; } currentNode.count += sample.count || 1; currentNode.heapSize += sample.heapSize; } } export class NativeMemory { index: number = 0; eventId: number = 0; eventType: string = ''; subType: string = ''; subTypeId: number = 0; addr: string = ''; startTs: number = 0; endTs: number = 0; heapSize: number = 0; symbol: string = ''; library: string = ''; lastLibId: number = 0; isSelected: boolean = false; threadId: number = 0; } export class HeapStruct { startTime: number | undefined; endTime: number | undefined; dur: number | undefined; density: number | undefined; heapsize: number | undefined; maxHeapSize: number = 0; maxDensity: number = 0; minHeapSize: number = 0; minDensity: number = 0; } export class NativeEvent { startTime: number = 0; heapSize: number = 0; eventType: number = 0; } export class StatisticsSelection { memoryTap: string = ''; max: number = 0; } class AnalysisSample { id: number; count: number; size: number; type: number; startTs: number; isRelease: boolean; releaseCount?: number; releaseSize?: number; endTs?: number; subType?: string; tid?: number; addr?: string; libId!: number; libName!: string; symbolId!: number; symbolName!: string; constructor(id: number, size: number, count: number, type: number | string, startTs: number) { this.id = id; this.size = size; this.count = count; this.startTs = startTs; switch (type) { case 'AllocEvent': case '0': this.type = 0; this.isRelease = false; break; case 'MmapEvent': case '1': this.isRelease = false; this.type = 1; break; case 'FreeEvent': this.isRelease = true; this.type = 2; break; case 'MunmapEvent': this.isRelease = true; this.type = 3; break; default: this.isRelease = false; this.type = -1; } } }