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}