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}