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 */ 15import { 16 convertJSON, 17 DataCache, 18 formatRealDateMs, 19 getByteWithUnit, 20 getTimeString, 21 HeapTreeDataBean, 22 LogicHandler, 23 MerageBean, 24 merageBeanDataSplit, 25 postMessage, 26 setFileName, 27} from './ProcedureLogicWorkerCommon.js'; 28export class ProcedureLogicWorkerNativeMemory extends LogicHandler { 29 selectTotalSize = 0; 30 selectTotalCount = 0; 31 stackCount = 0; 32 NATIVE_MEMORY_DATA: Array<NativeEvent> = []; 33 currentTreeMapData: any = {}; 34 currentTreeList: any[] = []; 35 queryAllCallchainsSamples: NativeHookStatistics[] = []; 36 currentSamples: NativeHookStatistics[] = []; 37 allThreads: NativeHookCallInfo[] = []; 38 splitMapData: any = {}; 39 searchValue: string = ''; 40 currentEventId: string = ''; 41 chartComplete: Map<number, boolean> = new Map<number, boolean>(); 42 realTimeDif: number = 0; 43 responseTypes: { key: number; value: string }[] = []; 44 totalNS: number = 0; 45 isAnalysis: boolean = false; 46 isStatistic: boolean = false; 47 private dataCache = DataCache.getInstance(); 48 handle(data: any): void { 49 this.currentEventId = data.id; 50 if (data && data.type) { 51 switch (data.type) { 52 case 'native-memory-init': 53 this.clearAll(); 54 if (data.params.isRealtime) { 55 this.realTimeDif = data.params.realTimeDif; 56 } 57 this.dataCache.dataDict = data.params.dataDict; 58 this.initNMChartData(); 59 break; 60 case 'native-memory-queryNMChartData': 61 this.NATIVE_MEMORY_DATA = convertJSON(data.params.list) || []; 62 this.initNMFrameData(); 63 break; 64 case 'native-memory-queryNMFrameData': 65 let arr = convertJSON(data.params.list) || []; 66 this.initNMStack(arr); 67 arr = []; 68 self.postMessage({ 69 id: data.id, 70 action: 'native-memory-init', 71 results: [], 72 }); 73 break; 74 case 'native-memory-queryCallchainsSamples': 75 this.searchValue = ''; 76 if (data.params.list) { 77 let callchainsSamples = convertJSON(data.params.list) || []; 78 this.queryAllCallchainsSamples = callchainsSamples; 79 this.freshCurrentCallchains(callchainsSamples, true); 80 // @ts-ignore 81 self.postMessage({ 82 id: data.id, 83 action: data.action, 84 results: this.allThreads, 85 }); 86 } else { 87 this.queryCallchainsSamples( 88 'native-memory-queryCallchainsSamples', 89 data.params.leftNs, 90 data.params.rightNs, 91 data.params.types 92 ); 93 } 94 break; 95 case 'native-memory-queryStatisticCallchainsSamples': 96 this.searchValue = ''; 97 if (data.params.list) { 98 let samples = convertJSON(data.params.list) || []; 99 this.queryAllCallchainsSamples = samples; 100 this.freshCurrentCallchains(samples, true); 101 // @ts-ignore 102 self.postMessage({ 103 id: data.id, 104 action: data.action, 105 results: this.allThreads, 106 }); 107 } else { 108 this.queryStatisticCallchainsSamples( 109 'native-memory-queryStatisticCallchainsSamples', 110 data.params.leftNs, 111 data.params.rightNs, 112 data.params.types 113 ); 114 } 115 break; 116 case 'native-memory-queryAnalysis': 117 if (data.params.list) { 118 let samples = convertJSON(data.params.list) || []; 119 this.queryAllCallchainsSamples = samples; 120 self.postMessage({ 121 id: data.id, 122 action: data.action, 123 results: this.combineStatisticAndCallChain(samples), 124 }); 125 } else { 126 if (data.params.isStatistic) { 127 this.isStatistic = true; 128 this.queryStatisticCallchainsSamples( 129 'native-memory-queryAnalysis', 130 data.params.leftNs, 131 data.params.rightNs, 132 data.params.types 133 ); 134 } else { 135 this.isStatistic = false; 136 this.queryCallchainsSamples( 137 'native-memory-queryAnalysis', 138 data.params.leftNs, 139 data.params.rightNs, 140 data.params.types 141 ); 142 } 143 } 144 break; 145 case 'native-memory-action': 146 if (data.params) { 147 // @ts-ignore 148 self.postMessage({ 149 id: data.id, 150 action: data.action, 151 results: this.resolvingAction(data.params), 152 }); 153 } 154 break; 155 case 'native-memory-chart-action': 156 if (data.params) { 157 postMessage(data.id, data.action, this.resolvingActionNativeMemoryChartData(data.params)); 158 } 159 break; 160 case 'native-memory-calltree-action': 161 if (data.params) { 162 self.postMessage({ 163 id: data.id, 164 action: data.action, 165 results: this.resolvingNMCallAction(data.params), 166 }); 167 } 168 break; 169 case 'native-memory-init-responseType': 170 this.initResponseTypeList(data.params); 171 self.postMessage({ 172 id: data.id, 173 action: data.action, 174 results: [], 175 }); 176 break; 177 case 'native-memory-get-responseType': 178 self.postMessage({ 179 id: data.id, 180 action: data.action, 181 results: this.responseTypes, 182 }); 183 break; 184 case 'native-memory-queryNativeHookStatistic': 185 if (data.params.list) { 186 let arr = this.statisticDataHandler(convertJSON(data.params.list)); 187 postMessage(data.id, data.action, this.handleNativeHookStatisticData(arr)); 188 } else { 189 this.totalNS = data.params.totalNS; 190 this.queryNativeHookStatistic(data.params.type); 191 } 192 break; 193 } 194 } 195 } 196 197 initNMChartData() { 198 this.queryData( 199 this.currentEventId, 200 'native-memory-queryNMChartData', 201 ` 202 select * from ( 203 select 204 h.start_ts - t.start_ts as startTime, 205 h.heap_size as heapSize, 206 (case when h.event_type = 'AllocEvent' then 0 else 1 end) as eventType 207 from native_hook h ,trace_range t 208 where h.start_ts between t.start_ts and t.end_ts 209 and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent') 210 union all 211 select 212 h.end_ts - t.start_ts as startTime, 213 h.heap_size as heapSize, 214 (case when h.event_type = 'AllocEvent' then 2 else 3 end) as eventType 215 from native_hook h ,trace_range t 216 where 217 h.start_ts between t.start_ts and t.end_ts 218 and h.end_ts between t.start_ts and t.end_ts 219 and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent') 220 ) 221 order by startTime; 222 `, 223 {} 224 ); 225 } 226 queryNativeHookStatistic(type: number) { 227 let condition = ''; 228 if (type === 0) { 229 condition = 'and type = 0'; 230 } else if (type === 1) { 231 condition = 'and type > 0'; 232 } else { 233 condition = ''; 234 } 235 let sql = ` 236select callchain_id callchainId, 237 ts - start_ts as ts, 238 apply_count applyCount, 239 apply_size applySize, 240 release_count releaseCount, 241 release_size releaseSize 242from native_hook_statistic,trace_range 243where ts between start_ts and end_ts ${condition}; 244 `; 245 this.queryData(this.currentEventId, 'native-memory-queryNativeHookStatistic', sql, {}); 246 } 247 248 statisticDataHandler(arr: Array<any>) { 249 let callGroupMap: Map<number, any[]> = new Map<number, any[]>(); 250 let obj = {}; 251 for (let hook of arr) { 252 if ((obj as any)[hook.ts]) { 253 let data = (obj as any)[hook.ts] as any; 254 data.startTime = hook.ts; 255 data.dur = 0; 256 if (callGroupMap.has(hook.callchainId)) { 257 let calls = callGroupMap.get(hook.callchainId); 258 let last = calls![calls!.length - 1]; 259 data.heapsize += hook.applySize - last.applySize - (hook.releaseSize - last.releaseSize); 260 data.density += hook.applyCount - last.applyCount - (hook.releaseCount - last.releaseCount); 261 calls!.push(hook); 262 } else { 263 data.heapsize += hook.applySize - hook.releaseSize; 264 data.density += hook.applyCount - hook.releaseCount; 265 callGroupMap.set(hook.callchainId, [hook]); 266 } 267 } else { 268 let data: any = {}; 269 data.startTime = hook.ts; 270 data.dur = 0; 271 if (callGroupMap.has(hook.callchainId)) { 272 let calls = callGroupMap.get(hook.callchainId); 273 let last = calls![calls!.length - 1]; 274 data.heapsize = hook.applySize - last.applySize - (hook.releaseSize - last.releaseSize); 275 data.density = hook.applyCount - last.applyCount - (hook.releaseCount - last.releaseCount); 276 calls!.push(hook); 277 } else { 278 data.heapsize = hook.applySize - hook.releaseSize; 279 data.density = hook.applyCount - hook.releaseCount; 280 callGroupMap.set(hook.callchainId, [hook]); 281 } 282 (obj as any)[hook.ts] = data; 283 } 284 } 285 return Object.values(obj) as { 286 startTime: number; 287 heapsize: number; 288 density: number; 289 dur: number; 290 }[]; 291 } 292 293 handleNativeHookStatisticData( 294 arr: { 295 startTime: number; 296 heapsize: number; 297 density: number; 298 dur: number; 299 }[] 300 ) { 301 let maxSize = 0, 302 maxDensity = 0, 303 minSize = 0, 304 minDensity = 0; 305 for (let i = 0, len = arr.length; i < len; i++) { 306 if (i == len - 1) { 307 arr[i].dur = this.totalNS - arr[i].startTime; 308 } else { 309 arr[i + 1].heapsize = arr[i].heapsize + arr[i + 1].heapsize; 310 arr[i + 1].density = arr[i].density + arr[i + 1].density; 311 arr[i].dur = arr[i + 1].startTime - arr[i].startTime; 312 } 313 maxSize = Math.max(maxSize, arr[i].heapsize); 314 maxDensity = Math.max(maxDensity, arr[i].density); 315 minSize = Math.min(minSize, arr[i].heapsize); 316 minDensity = Math.min(minDensity, arr[i].density); 317 } 318 return arr.map((it) => { 319 (it as any).maxHeapSize = maxSize; 320 (it as any).maxDensity = maxDensity; 321 (it as any).minHeapSize = minSize; 322 (it as any).minDensity = minDensity; 323 return it; 324 }); 325 } 326 initResponseTypeList(list: any[]) { 327 this.responseTypes = [ 328 { 329 key: -1, 330 value: 'ALL', 331 }, 332 ]; 333 list.forEach((item) => { 334 if (item.lastLibId == null) { 335 this.responseTypes.push({ 336 key: 0, 337 value: '-', 338 }); 339 } else { 340 this.responseTypes.push({ 341 key: item.lastLibId, 342 value: this.groupCutFilePath(item.lastLibId, item.value) || '-', 343 }); 344 } 345 }); 346 } 347 initNMFrameData() { 348 this.queryData( 349 this.currentEventId, 350 'native-memory-queryNMFrameData', 351 ` 352 select h.symbol_id as symbolId, h.file_id as fileId, h.depth, h.callchain_id as eventId, h.vaddr as addr 353 from native_hook_frame h 354 `, 355 {} 356 ); 357 } 358 initNMStack(frameArr: Array<HeapTreeDataBean>) { 359 frameArr.map((frame) => { 360 let frameEventId = frame.eventId; 361 if (this.dataCache.nmHeapFrameMap.has(frameEventId)) { 362 this.dataCache.nmHeapFrameMap.get(frameEventId)!.push(frame); 363 } else { 364 this.dataCache.nmHeapFrameMap.set(frameEventId, [frame]); 365 } 366 }); 367 } 368 resolvingAction(paramMap: Map<string, any>): Array<NativeHookCallInfo | NativeMemory | HeapStruct> { 369 let actionType = paramMap.get('actionType'); 370 if (actionType == 'call-info') { 371 return this.resolvingActionCallInfo(paramMap); 372 } else if (actionType == 'native-memory') { 373 return this.resolvingActionNativeMemory(paramMap); 374 } else if (actionType == 'memory-stack') { 375 return this.resolvingActionNativeMemoryStack(paramMap); 376 } else { 377 return []; 378 } 379 } 380 resolvingActionNativeMemoryChartData(paramMap: Map<string, any>): Array<HeapStruct> { 381 let nativeMemoryType: number = paramMap.get('nativeMemoryType') as number; 382 let totalNS: number = paramMap.get('totalNS') as number; 383 let arr: Array<HeapStruct> = []; 384 let maxSize = 0, 385 maxDensity = 0, 386 minSize = 0, 387 minDensity = 0; 388 let tempSize = 0, 389 tempDensity = 0; 390 let filterLen = 0, 391 filterLevel = 0; 392 let putArr = (ne: NativeEvent, filterLevel: number, finish: boolean) => { 393 let heap = new HeapStruct(); 394 heap.startTime = ne.startTime; 395 if (arr.length == 0) { 396 if (ne.eventType == 0 || ne.eventType == 1) { 397 heap.density = 1; 398 heap.heapsize = ne.heapSize; 399 } else { 400 heap.density = -1; 401 heap.heapsize = 0 - ne.heapSize; 402 } 403 maxSize = heap.heapsize; 404 maxDensity = heap.density; 405 minSize = heap.heapsize; 406 minDensity = heap.density; 407 arr.push(heap); 408 } else { 409 let last = arr[arr.length - 1]; 410 last.dur = heap.startTime! - last.startTime!; 411 if (last.dur > filterLevel || finish) { 412 if (ne.eventType == 0 || ne.eventType == 1) { 413 heap.density = last.density! + tempDensity + 1; 414 heap.heapsize = last.heapsize! + tempSize + ne.heapSize; 415 } else { 416 heap.density = last.density! + tempDensity - 1; 417 heap.heapsize = last.heapsize! + tempSize - ne.heapSize; 418 } 419 tempDensity = 0; 420 tempSize = 0; 421 if (heap.density > maxDensity) { 422 maxDensity = heap.density; 423 } 424 if (heap.density < minDensity) { 425 minDensity = heap.density; 426 } 427 if (heap.heapsize > maxSize) { 428 maxSize = heap.heapsize; 429 } 430 if (heap.heapsize < minSize) { 431 minSize = heap.heapsize; 432 } 433 arr.push(heap); 434 } else { 435 if (ne.eventType == 0 || ne.eventType == 1) { 436 tempDensity = tempDensity + 1; 437 tempSize = tempSize + ne.heapSize; 438 } else { 439 tempDensity = tempDensity - 1; 440 tempSize = tempSize - ne.heapSize; 441 } 442 } 443 } 444 }; 445 if (nativeMemoryType == 1) { 446 let temp = this.NATIVE_MEMORY_DATA.filter((ne) => ne.eventType === 0 || ne.eventType === 2); 447 filterLen = temp.length; 448 filterLevel = this.getFilterLevel(filterLen); 449 temp.map((ne, index) => putArr(ne, filterLevel, index === filterLen - 1)); 450 temp.length = 0; 451 } else if (nativeMemoryType == 2) { 452 let temp = this.NATIVE_MEMORY_DATA.filter((ne) => ne.eventType === 1 || ne.eventType === 3); 453 filterLen = temp.length; 454 filterLevel = this.getFilterLevel(filterLen); 455 temp.map((ne, index) => putArr(ne, filterLevel, index === filterLen - 1)); 456 temp.length = 0; 457 } else { 458 filterLen = this.NATIVE_MEMORY_DATA.length; 459 let filterLevel = this.getFilterLevel(filterLen); 460 this.NATIVE_MEMORY_DATA.map((ne, index) => putArr(ne, filterLevel, index === filterLen - 1)); 461 } 462 if (arr.length > 0) { 463 arr[arr.length - 1].dur = totalNS - arr[arr.length - 1].startTime!; 464 } 465 arr.map((heap) => { 466 heap.maxHeapSize = maxSize; 467 heap.maxDensity = maxDensity; 468 heap.minHeapSize = minSize; 469 heap.minDensity = minDensity; 470 }); 471 this.chartComplete.set(nativeMemoryType, true); 472 if (this.chartComplete.has(0) && this.chartComplete.has(1) && this.chartComplete.has(2)) { 473 this.NATIVE_MEMORY_DATA = []; 474 } 475 return arr; 476 } 477 resolvingActionNativeMemoryStack(paramMap: Map<string, any>) { 478 let eventId = paramMap.get('eventId'); 479 let frameArr = this.dataCache.nmHeapFrameMap.get(eventId) || []; 480 let arr: Array<NativeHookCallInfo> = []; 481 frameArr.map((frame) => { 482 let target = new NativeHookCallInfo(); 483 target.eventId = frame.eventId; 484 target.depth = frame.depth; 485 target.addr = frame.addr; 486 target.symbol = this.groupCutFilePath(frame.symbolId, this.dataCache.dataDict.get(frame.symbolId) || '') ?? ''; 487 target.library = this.groupCutFilePath(frame.fileId, this.dataCache.dataDict.get(frame.fileId) || '') ?? ''; 488 target.title = `[ ${target.symbol} ] ${target.library}`; 489 target.type = 490 target.library.endsWith('.so.1') || target.library.endsWith('.dll') || target.library.endsWith('.so') ? 0 : 1; 491 arr.push(target); 492 }); 493 return arr; 494 } 495 resolvingActionNativeMemory(paramMap: Map<string, any>): Array<NativeMemory> { 496 let dataSource = paramMap.get('data') as Array<NativeHookStatistics>; 497 let filterAllocType = paramMap.get('filterAllocType'); 498 let filterEventType = paramMap.get('filterEventType'); 499 let filterResponseType = paramMap.get('filterResponseType'); 500 let leftNs = paramMap.get('leftNs'); 501 let rightNs = paramMap.get('rightNs'); 502 let statisticsSelection = paramMap.get('statisticsSelection'); 503 let filter = dataSource.filter((item) => { 504 if (item.subTypeId != null && item.subType == undefined) { 505 item.subType = this.dataCache.dataDict.get(item.subTypeId) || '-'; 506 } 507 let filterAllocation = true; 508 if (filterAllocType == '1') { 509 filterAllocation = 510 item.startTs >= leftNs && 511 item.startTs <= rightNs && 512 (item.endTs > rightNs || item.endTs == 0 || item.endTs == null); 513 } else if (filterAllocType == '2') { 514 filterAllocation = 515 item.startTs >= leftNs && 516 item.startTs <= rightNs && 517 item.endTs <= rightNs && 518 item.endTs != 0 && 519 item.endTs != null; 520 } 521 let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection); 522 let filterLastLib = filterResponseType == -1 ? true : filterResponseType == item.lastLibId; 523 return filterAllocation && filterNative && filterLastLib; 524 }); 525 let data: Array<NativeMemory> = []; 526 for (let i = 0, len = filter.length; i < len; i++) { 527 let hook = filter[i]; 528 let memory = new NativeMemory(); 529 memory.index = i; 530 memory.eventId = hook.eventId; 531 memory.eventType = hook.eventType; 532 memory.subType = hook.subType; 533 memory.heapSize = hook.heapSize; 534 memory.endTs = hook.endTs; 535 memory.heapSizeUnit = getByteWithUnit(hook.heapSize); 536 memory.addr = '0x' + hook.addr; 537 memory.startTs = hook.startTs; 538 memory.timestamp = 539 this.realTimeDif == 0 ? getTimeString(hook.startTs) : formatRealDateMs(hook.startTs + this.realTimeDif); 540 memory.state = hook.endTs > leftNs && hook.endTs <= rightNs ? 'Freed' : 'Existing'; 541 memory.threadId = hook.tid; 542 memory.threadName = hook.threadName; 543 memory.lastLibId = hook.lastLibId; 544 (memory as any).isSelected = hook.isSelected; 545 let arr = this.dataCache.nmHeapFrameMap.get(hook.eventId) || []; 546 let frame = Array.from(arr) 547 .reverse() 548 .find((item) => { 549 let fileName = this.dataCache.dataDict.get(item.fileId); 550 return !((fileName ?? '').includes('libc++') || (fileName ?? '').includes('musl')); 551 }); 552 if (frame == null || frame == undefined) { 553 if (arr.length > 0) { 554 frame = arr[0]; 555 } 556 } 557 if (frame != null && frame != undefined) { 558 memory.symbol = this.groupCutFilePath(frame.symbolId, this.dataCache.dataDict.get(frame.symbolId) || ''); 559 memory.library = this.groupCutFilePath( 560 frame.fileId, 561 this.dataCache.dataDict.get(frame.fileId) || 'Unknown Path' 562 ); 563 } else { 564 memory.symbol = '-'; 565 memory.library = '-'; 566 } 567 data.push(memory); 568 } 569 return data; 570 } 571 resolvingActionCallInfo(paramMap: Map<string, any>): Array<NativeHookCallInfo> { 572 let dataSource = paramMap.get('data') as Array<NativeHookStatistics>; 573 let filterAllocType = paramMap.get('filterAllocType'); 574 let filterEventType = paramMap.get('filterEventType'); 575 let leftNs = paramMap.get('leftNs'); 576 let rightNs = paramMap.get('rightNs'); 577 let filter: Array<NativeHookStatistics> = []; 578 dataSource.map((item) => { 579 let filterAllocation = true; 580 let filterNative = true; 581 if (filterAllocType == '1') { 582 filterAllocation = 583 item.startTs >= leftNs && 584 item.startTs <= rightNs && 585 (item.endTs > rightNs || item.endTs == 0 || item.endTs == null); 586 } else if (filterAllocType == '2') { 587 filterAllocation = 588 item.startTs >= leftNs && 589 item.startTs <= rightNs && 590 item.endTs <= rightNs && 591 item.endTs != 0 && 592 item.endTs != null; 593 } 594 if (filterEventType == '1') { 595 filterNative = item.eventType == 'AllocEvent'; 596 } else if (filterEventType == '2') { 597 filterNative = item.eventType == 'MmapEvent'; 598 } 599 if (filterAllocation && filterNative) { 600 filter.push(item); 601 } 602 }); 603 this.freshCurrentCallchains(filter, true); 604 return this.allThreads; 605 } 606 groupCutFilePath(fileId: number, path: string): string { 607 let name = ''; 608 if (this.dataCache.nmFileDict.has(fileId)) { 609 name = this.dataCache.nmFileDict.get(fileId) ?? ''; 610 } else { 611 let currentPath = path.substring(path.lastIndexOf('/') + 1); 612 this.dataCache.nmFileDict.set(fileId, currentPath); 613 name = currentPath; 614 } 615 return name == '' ? '-' : name; 616 } 617 mergeTree(target: NativeHookCallInfo, src: NativeHookCallInfo) { 618 let len = src.children.length; 619 src.size += target.size; 620 src.heapSizeStr = `${getByteWithUnit(src!.size)}`; 621 src.heapPercent = `${((src!.size / this.selectTotalSize) * 100).toFixed(1)}%`; 622 if (len == 0) { 623 src.children.push(target); 624 } else { 625 let index = src.children.findIndex((hook) => hook.symbol == target.symbol && hook.depth == target.depth); 626 if (index != -1) { 627 let srcChild = <NativeHookCallInfo>src.children[index]; 628 srcChild.count += target.count; 629 srcChild!.countValue = `${srcChild.count}`; 630 srcChild!.countPercent = `${((srcChild!.count / this.selectTotalCount) * 100).toFixed(1)}%`; 631 if (target.children.length > 0) { 632 this.mergeTree(<NativeHookCallInfo>target.children[0], <NativeHookCallInfo>srcChild); 633 } else { 634 srcChild.size += target.size; 635 srcChild.heapSizeStr = `${getByteWithUnit(src!.size)}`; 636 srcChild.heapPercent = `${((srcChild!.size / this.selectTotalSize) * 100).toFixed(1)}%`; 637 } 638 } else { 639 src.children.push(target); 640 } 641 } 642 } 643 traverseSampleTree(stack: NativeHookCallInfo, hook: NativeHookStatistics) { 644 stack.count += 1; 645 stack.countValue = `${stack.count}`; 646 stack.countPercent = `${((stack.count / this.selectTotalCount) * 100).toFixed(1)}%`; 647 stack.size += hook.heapSize; 648 stack.tid = hook.tid; 649 stack.threadName = hook.threadName; 650 stack.heapSizeStr = `${getByteWithUnit(stack.size)}`; 651 stack.heapPercent = `${((stack.size / this.selectTotalSize) * 100).toFixed(1)}%`; 652 if (stack.children.length > 0) { 653 stack.children.map((child) => { 654 this.traverseSampleTree(child as NativeHookCallInfo, hook); 655 }); 656 } 657 } 658 traverseTree(stack: NativeHookCallInfo, hook: NativeHookStatistics) { 659 stack.count = 1; 660 stack.countValue = `${stack.count}`; 661 stack.countPercent = `${((stack!.count / this.selectTotalCount) * 100).toFixed(1)}%`; 662 stack.size = hook.heapSize; 663 stack.tid = hook.tid; 664 stack.threadName = hook.threadName; 665 stack.heapSizeStr = `${getByteWithUnit(stack!.size)}`; 666 stack.heapPercent = `${((stack!.size / this.selectTotalSize) * 100).toFixed(1)}%`; 667 if (stack.children.length > 0) { 668 stack.children.map((child) => { 669 this.traverseTree(child as NativeHookCallInfo, hook); 670 }); 671 } 672 } 673 getTypeFromIndex( 674 indexOf: number, 675 item: NativeHookStatistics, 676 statisticsSelection: Array<StatisticsSelection> 677 ): boolean { 678 if (indexOf == -1) { 679 return false; 680 } 681 if (indexOf < 3) { 682 if (indexOf == 0) { 683 return true; 684 } else if (indexOf == 1) { 685 return item.eventType == 'AllocEvent'; 686 } else if (indexOf == 2) { 687 return item.eventType == 'MmapEvent'; 688 } 689 } else if (indexOf - 3 < statisticsSelection.length) { 690 let selectionElement = statisticsSelection[indexOf - 3]; 691 if (selectionElement.memoryTap != undefined && selectionElement.max != undefined) { 692 if (selectionElement.memoryTap.indexOf('Malloc') != -1) { 693 return item.eventType == 'AllocEvent' && item.heapSize == selectionElement.max; 694 } else if (selectionElement.memoryTap.indexOf('Mmap') != -1) { 695 return item.eventType == 'MmapEvent' && item.heapSize == selectionElement.max && item.subTypeId === null; 696 } else { 697 return item.subType == selectionElement.memoryTap; 698 } 699 } 700 if (selectionElement.max === undefined && typeof selectionElement.memoryTap === 'number') { 701 return item.subTypeId === selectionElement.memoryTap; 702 } 703 } 704 return false; 705 } 706 clearAll() { 707 this.dataCache.clearNM(); 708 this.splitMapData = {}; 709 this.currentSamples = []; 710 this.allThreads = []; 711 this.queryAllCallchainsSamples = []; 712 this.NATIVE_MEMORY_DATA = []; 713 this.chartComplete.clear(); 714 this.realTimeDif = 0; 715 this.currentTreeMapData = {}; 716 this.currentTreeList.length = 0; 717 this.responseTypes.length = 0; 718 } 719 720 queryCallchainsSamples(action: string, leftNs: number, rightNs: number, types: Array<string>) { 721 this.queryData( 722 this.currentEventId, 723 action, 724 `select A.id, 725 callchain_id as eventId, 726 event_type as eventType, 727 heap_size as heapSize, 728 (A.start_ts - B.start_ts) as startTs, 729 (A.end_ts - B.start_ts) as endTs, 730 tid, 731 ifnull(last_lib_id,0) as lastLibId, 732 t.name as threadName, 733 A.addr, 734 A.sub_type_id as subTypeId 735 from 736 native_hook A, 737 trace_range B 738 left join 739 thread t 740 on 741 A.itid = t.id 742 where 743 A.start_ts - B.start_ts 744 between ${leftNs} and ${rightNs} and A.event_type in (${types.join(',')}) 745 `, 746 {} 747 ); 748 } 749 queryStatisticCallchainsSamples(action: string, leftNs: number, rightNs: number, types: Array<number>) { 750 let condition = ''; 751 if (types.length === 1) { 752 if (types[0] === 0) { 753 condition = 'and type = 0'; 754 } else { 755 condition = 'and type != 0'; 756 } 757 } 758 this.queryData( 759 this.currentEventId, 760 action, 761 `select A.id, 762 0 as tid, 763 callchain_id as eventId, 764 (case when type = 0 then 'AllocEvent' else 'MmapEvent' end) as eventType, 765 type as subTypeId, 766 apply_size as heapSize, 767 release_size as freeSize, 768 apply_count as count, 769 release_count as freeCount, 770 (max(A.ts) - B.start_ts) as startTs 771 from 772 native_hook_statistic A, 773 trace_range B 774 where 775 A.ts - B.start_ts 776 between ${leftNs} and ${rightNs} 777 ${condition} 778 group by callchain_id; 779 `, 780 {} 781 ); 782 } 783 784 combineStatisticAndCallChain(samples: NativeHookStatistics[]) { 785 samples.sort((a, b) => a.id - b.id); 786 const analysisSampleList = new Array<AnalysisSample>(); 787 const applyAllocSamples = new Array<AnalysisSample>(); 788 const applyMmapSamples = new Array<AnalysisSample>(); 789 790 for (const sample of samples) { 791 const count = this.isStatistic ? sample.count : 1; 792 const analysisSample = new AnalysisSample(sample.id, sample.heapSize, count, sample.eventType, sample.startTs); 793 794 if (this.isStatistic) { 795 analysisSample.releaseCount = sample.freeCount; 796 analysisSample.releaseSize = sample.freeSize; 797 switch (sample.subTypeId) { 798 case 1: 799 analysisSample.subType = 'MmapEvent'; 800 break; 801 case 2: 802 analysisSample.subType = 'FILE_PAGE_MSG'; 803 break; 804 case 3: 805 analysisSample.subType = 'MEMORY_USING_MSG'; 806 break; 807 default: 808 analysisSample.subType = undefined; 809 } 810 } else { 811 let subType = undefined; 812 if (sample.subTypeId) { 813 subType = this.dataCache.dataDict.get(sample.subTypeId); 814 } 815 analysisSample.endTs = sample.endTs; 816 analysisSample.addr = sample.addr; 817 analysisSample.tid = sample.tid; 818 analysisSample.subType = subType; 819 } 820 821 if (['FreeEvent', 'MunmapEvent'].includes(sample.eventType)) { 822 if (sample.eventType === 'FreeEvent') { 823 this.setApplyIsRelease(analysisSample, applyAllocSamples); 824 } else { 825 this.setApplyIsRelease(analysisSample, applyMmapSamples); 826 } 827 continue; 828 } else { 829 if (sample.eventType === 'AllocEvent') { 830 applyAllocSamples.push(analysisSample); 831 } else { 832 applyMmapSamples.push(analysisSample); 833 } 834 } 835 836 const callChains = this.dataCache.nmHeapFrameMap.get(sample.eventId) || []; 837 if (!callChains || callChains.length === 0) { 838 return; 839 } 840 let index = callChains.length - 1; 841 let lastFilterCallChain: HeapTreeDataBean | undefined | null; 842 while (true) { 843 // if all call stack is musl or libc++. use stack top lib 844 if (index < 0) { 845 lastFilterCallChain = callChains[callChains.length - 1]; 846 break; 847 } 848 849 lastFilterCallChain = callChains[index]; 850 const libPath = this.dataCache.dataDict.get(lastFilterCallChain.fileId); 851 //ignore musl and libc++ so 852 if (libPath?.includes('musl') || libPath?.includes('libc++')) { 853 index--; 854 } else { 855 lastFilterCallChain = lastFilterCallChain; 856 break; 857 } 858 } 859 860 const filePath = this.dataCache.dataDict.get(lastFilterCallChain.fileId)!; 861 let libName = ''; 862 if (filePath) { 863 const path = filePath.split('/'); 864 libName = path[path.length - 1]; 865 } 866 const symbolName = 867 this.dataCache.dataDict.get(lastFilterCallChain.symbolId) || libName + ' (' + sample.addr + ')'; 868 869 analysisSample.libId = lastFilterCallChain.fileId; 870 analysisSample.libName = libName; 871 analysisSample.symbolId = lastFilterCallChain.symbolId; 872 analysisSample.symbolName = symbolName; 873 874 analysisSampleList.push(analysisSample); 875 } 876 return analysisSampleList; 877 } 878 879 setApplyIsRelease(sample: AnalysisSample, arr: Array<AnalysisSample>) { 880 let idx = arr.length - 1; 881 for (idx; idx >= 0; idx--) { 882 let item = arr[idx]; 883 if (item.endTs === sample.startTs && item.addr === sample.addr) { 884 arr.splice(idx, 1); 885 item.isRelease = true; 886 return; 887 } 888 } 889 } 890 891 freshCurrentCallchains(samples: NativeHookStatistics[], isTopDown: boolean) { 892 this.currentTreeMapData = {}; 893 this.currentTreeList = []; 894 let totalSize = 0; 895 let totalCount = 0; 896 samples.forEach((sample) => { 897 if (sample.eventId == -1) { 898 return; 899 } 900 totalSize += sample.heapSize; 901 totalCount += sample.count || 1; 902 let callChains = this.createThreadSample(sample); 903 let topIndex = isTopDown ? 0 : callChains.length - 1; 904 if (callChains.length > 0) { 905 let root = 906 this.currentTreeMapData[ 907 sample.tid + '-' + (callChains[topIndex].symbolId || '') + '-' + (callChains[topIndex].fileId || '') 908 ]; 909 if (root == undefined) { 910 root = new NativeHookCallInfo(); 911 root.threadName = sample.threadName; 912 this.currentTreeMapData[ 913 sample.tid + '-' + (callChains[topIndex].symbolId || '') + '-' + (callChains[topIndex].fileId || '') 914 ] = root; 915 this.currentTreeList.push(root); 916 } 917 NativeHookCallInfo.merageCallChainSample(root, callChains[topIndex], sample); 918 if (callChains.length > 1) { 919 this.merageChildrenByIndex(root, callChains, topIndex, sample, isTopDown); 920 } 921 } 922 }); 923 let rootMerageMap: any = {}; 924 // @ts-ignore 925 let threads = Object.values(this.currentTreeMapData); 926 threads.forEach((merageData: any) => { 927 if (rootMerageMap[merageData.tid] == undefined) { 928 let threadMerageData = new NativeHookCallInfo(); //新增进程的节点数据 929 threadMerageData.canCharge = false; 930 threadMerageData.type = -1; 931 threadMerageData.symbolName = `${merageData.threadName || 'Thread'} [${merageData.tid}]`; 932 threadMerageData.symbol = threadMerageData.symbolName; 933 threadMerageData.children.push(merageData); 934 threadMerageData.initChildren.push(merageData); 935 threadMerageData.count = merageData.count || 1; 936 threadMerageData.heapSize = merageData.heapSize; 937 threadMerageData.totalCount = totalCount; 938 threadMerageData.totalSize = totalSize; 939 rootMerageMap[merageData.tid] = threadMerageData; 940 } else { 941 rootMerageMap[merageData.tid].children.push(merageData); 942 rootMerageMap[merageData.tid].initChildren.push(merageData); 943 rootMerageMap[merageData.tid].count += merageData.count || 1; 944 rootMerageMap[merageData.tid].heapSize += merageData.heapSize; 945 rootMerageMap[merageData.tid].totalCount = totalCount; 946 rootMerageMap[merageData.tid].totalSize = totalSize; 947 } 948 merageData.parentNode = rootMerageMap[merageData.tid]; //子节点添加父节点的引用 949 }); 950 let id = 0; 951 this.currentTreeList.forEach((node) => { 952 node.totalCount = totalCount; 953 node.totalSize = totalSize; 954 this.setMerageName(node); 955 if (node.id == '') { 956 node.id = id + ''; 957 id++; 958 } 959 if (node.parentNode) { 960 if (node.parentNode.id == '') { 961 node.parentNode.id = id + ''; 962 id++; 963 } 964 node.parentId = node.parentNode.id; 965 } 966 }); 967 // @ts-ignore 968 this.allThreads = Object.values(rootMerageMap) as NativeHookCallInfo[]; 969 } 970 groupCallchainSample(paramMap: Map<string, any>) { 971 let groupMap: any = {}; 972 let filterAllocType = paramMap.get('filterAllocType'); 973 let filterEventType = paramMap.get('filterEventType'); 974 let filterResponseType = paramMap.get('filterResponseType'); 975 let leftNs = paramMap.get('leftNs'); 976 let rightNs = paramMap.get('rightNs'); 977 let nativeHookType = paramMap.get('nativeHookType'); 978 let statisticsSelection = paramMap.get('statisticsSelection'); 979 if (filterAllocType == '0' && filterEventType == '0' && filterResponseType == -1) { 980 this.currentSamples = this.queryAllCallchainsSamples; 981 return; 982 } 983 let filter = this.queryAllCallchainsSamples.filter((item) => { 984 let filterAllocation = true; 985 if (nativeHookType === 'native-hook') { 986 if (filterAllocType == '1') { 987 filterAllocation = 988 item.startTs >= leftNs && 989 item.startTs <= rightNs && 990 (item.endTs > rightNs || item.endTs == 0 || item.endTs == null); 991 } else if (filterAllocType == '2') { 992 filterAllocation = 993 item.startTs >= leftNs && 994 item.startTs <= rightNs && 995 item.endTs <= rightNs && 996 item.endTs != 0 && 997 item.endTs != null; 998 } 999 } else { 1000 if (filterAllocType == '1') { 1001 filterAllocation = item.heapSize > item.freeSize; 1002 } else if (filterAllocType == '2') { 1003 filterAllocation = item.heapSize === item.freeSize; 1004 } 1005 } 1006 let filterLastLib = filterResponseType == -1 ? true : filterResponseType == item.lastLibId; 1007 let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection); 1008 return filterAllocation && filterNative && filterLastLib; 1009 }); 1010 filter.forEach((sample) => { 1011 let currentNode = groupMap[sample.tid + '-' + sample.eventId] || new NativeHookStatistics(); 1012 if (currentNode.count == 0) { 1013 Object.assign(currentNode, sample); 1014 if (filterAllocType == '1' && nativeHookType !== 'native-hook') { 1015 currentNode.heapSize = sample.heapSize - sample.freeSize; 1016 currentNode.count = sample.count - sample.freeCount; 1017 } 1018 if (currentNode.count === 0) { 1019 currentNode.count++; 1020 } 1021 } else { 1022 currentNode.count++; 1023 currentNode.heapSize += sample.heapSize; 1024 } 1025 groupMap[sample.tid + '-' + sample.eventId] = currentNode; 1026 }); 1027 // @ts-ignore 1028 this.currentSamples = Object.values(groupMap); 1029 } 1030 createThreadSample(sample: NativeHookStatistics) { 1031 return this.dataCache.nmHeapFrameMap.get(sample.eventId) || []; 1032 } 1033 merageChildrenByIndex( 1034 currentNode: NativeHookCallInfo, 1035 callChainDataList: any[], 1036 index: number, 1037 sample: NativeHookStatistics, 1038 isTopDown: boolean 1039 ) { 1040 isTopDown ? index++ : index--; 1041 let isEnd = isTopDown ? callChainDataList.length == index + 1 : index == 0; 1042 let node; 1043 if ( 1044 currentNode.initChildren.filter((child: any) => { 1045 if (child.symbolId == callChainDataList[index]?.symbolId && child.fileId == callChainDataList[index]?.fileId) { 1046 node = child; 1047 NativeHookCallInfo.merageCallChainSample(child, callChainDataList[index], sample); 1048 return true; 1049 } 1050 return false; 1051 }).length == 0 1052 ) { 1053 node = new NativeHookCallInfo(); 1054 NativeHookCallInfo.merageCallChainSample(node, callChainDataList[index], sample); 1055 currentNode.children.push(node); 1056 currentNode.initChildren.push(node); 1057 this.currentTreeList.push(node); 1058 node.parentNode = currentNode; 1059 } 1060 if (node && !isEnd) this.merageChildrenByIndex(node, callChainDataList, index, sample, isTopDown); 1061 } 1062 setMerageName(currentNode: NativeHookCallInfo) { 1063 currentNode.symbol = 1064 this.groupCutFilePath(currentNode.symbolId, this.dataCache.dataDict.get(currentNode.symbolId) || '') ?? 'unknown'; 1065 currentNode.path = this.dataCache.dataDict.get(currentNode.fileId) || 'unknown'; 1066 currentNode.libName = setFileName(currentNode.path); 1067 currentNode.lib = currentNode.path; 1068 currentNode.symbolName = `[${currentNode.symbol}] ${currentNode.libName}`; 1069 currentNode.type = 1070 currentNode.libName.endsWith('.so.1') || 1071 currentNode.libName.endsWith('.dll') || 1072 currentNode.libName.endsWith('.so') 1073 ? 0 1074 : 1; 1075 } 1076 clearSplitMapData(symbolName: string) { 1077 delete this.splitMapData[symbolName]; 1078 } 1079 resolvingNMCallAction(params: any[]) { 1080 if (params.length > 0) { 1081 params.forEach((item) => { 1082 if (item.funcName && item.funcArgs) { 1083 switch (item.funcName) { 1084 case 'groupCallchainSample': 1085 this.groupCallchainSample(item.funcArgs[0] as Map<string, any>); 1086 break; 1087 case 'getCallChainsBySampleIds': 1088 this.freshCurrentCallchains(this.currentSamples, item.funcArgs[0]); 1089 break; 1090 case 'hideSystemLibrary': 1091 merageBeanDataSplit.hideSystemLibrary(this.allThreads, this.splitMapData); 1092 break; 1093 case 'hideNumMaxAndMin': 1094 merageBeanDataSplit.hideNumMaxAndMin( 1095 this.allThreads, 1096 this.splitMapData, 1097 item.funcArgs[0], 1098 item.funcArgs[1] 1099 ); 1100 break; 1101 case 'splitAllProcess': 1102 merageBeanDataSplit.splitAllProcess(this.allThreads, this.splitMapData, item.funcArgs[0]); 1103 break; 1104 case 'resetAllNode': 1105 merageBeanDataSplit.resetAllNode(this.allThreads, this.currentTreeList, this.searchValue); 1106 break; 1107 case 'resotreAllNode': 1108 merageBeanDataSplit.resotreAllNode(this.splitMapData, item.funcArgs[0]); 1109 break; 1110 case 'splitTree': 1111 merageBeanDataSplit.splitTree( 1112 this.splitMapData, 1113 this.allThreads, 1114 item.funcArgs[0], 1115 item.funcArgs[1], 1116 item.funcArgs[2], 1117 this.currentTreeList, 1118 this.searchValue 1119 ); 1120 break; 1121 case 'setSearchValue': 1122 this.searchValue = item.funcArgs[0]; 1123 break; 1124 case 'clearSplitMapData': 1125 this.clearSplitMapData(item.funcArgs[0]); 1126 break; 1127 } 1128 } 1129 }); 1130 } 1131 return this.allThreads.filter((thread) => { 1132 return thread.children && thread.children.length > 0; 1133 }); 1134 } 1135 getFilterLevel(len: number): number { 1136 if (len > 100_0000) { 1137 return 10_0000; 1138 } else if (len > 50_0000) { 1139 return 5_0000; 1140 } else if (len > 30_0000) { 1141 return 2_0000; 1142 } else if (len > 15_0000) { 1143 return 5000; 1144 } else { 1145 return 0; 1146 } 1147 } 1148} 1149 1150export class NativeHookStatistics { 1151 id: number = 0; 1152 eventId: number = 0; 1153 eventType: string = ''; 1154 subType: string = ''; 1155 subTypeId: number = 0; 1156 heapSize: number = 0; 1157 freeSize: number = 0; 1158 addr: string = ''; 1159 startTs: number = 0; 1160 endTs: number = 0; 1161 sumHeapSize: number = 0; 1162 max: number = 0; 1163 count: number = 0; 1164 freeCount: number = 0; 1165 tid: number = 0; 1166 threadName: string = ''; 1167 lastLibId: number = 0; 1168 isSelected: boolean = false; 1169} 1170export class NativeHookCallInfo extends MerageBean { 1171 #totalCount: number = 0; 1172 #totalSize: number = 0; 1173 library: string = ''; 1174 symbolId: number = 0; 1175 fileId: number = 0; 1176 title: string = ''; 1177 count: number = 0; 1178 countValue: string = ''; 1179 countPercent: string = ''; 1180 type: number = 0; 1181 heapSize: number = 0; 1182 heapPercent: string = ''; 1183 heapSizeStr: string = ''; 1184 eventId: number = 0; 1185 tid: number = 0; 1186 threadName: string = ''; 1187 eventType: string = ''; 1188 isSelected: boolean = false; 1189 set totalCount(total: number) { 1190 this.#totalCount = total; 1191 this.countValue = this.count + ''; 1192 this.size = this.heapSize; 1193 this.countPercent = `${((this.count / total) * 100).toFixed(1)}%`; 1194 } 1195 get totalCount() { 1196 return this.#totalCount; 1197 } 1198 set totalSize(total: number) { 1199 this.#totalSize = total; 1200 this.heapSizeStr = `${getByteWithUnit(this.heapSize)}`; 1201 this.heapPercent = `${((this.heapSize / total) * 100).toFixed(1)}%`; 1202 } 1203 get totalSize() { 1204 return this.#totalSize; 1205 } 1206 static merageCallChainSample( 1207 currentNode: NativeHookCallInfo, 1208 callChain: HeapTreeDataBean, 1209 sample: NativeHookStatistics 1210 ) { 1211 if (currentNode.symbol == undefined || currentNode.symbol == '') { 1212 currentNode.symbol = callChain.AllocationFunction || ''; 1213 currentNode.addr = callChain.addr; 1214 currentNode.eventId = sample.eventId; 1215 currentNode.eventType = sample.eventType; 1216 currentNode.symbolId = callChain.symbolId; 1217 currentNode.fileId = callChain.fileId; 1218 currentNode.tid = sample.tid; 1219 } 1220 currentNode.count += sample.count || 1; 1221 currentNode.heapSize += sample.heapSize; 1222 } 1223} 1224export class NativeMemory { 1225 index: number = 0; 1226 eventId: number = 0; 1227 eventType: string = ''; 1228 subType: string = ''; 1229 addr: string = ''; 1230 startTs: number = 0; 1231 endTs: number = 0; 1232 timestamp: string = ''; 1233 heapSize: number = 0; 1234 heapSizeUnit: string = ''; 1235 symbol: string = ''; 1236 library: string = ''; 1237 lastLibId: number = 0; 1238 isSelected: boolean = false; 1239 state: string = ''; 1240 threadId: number = 0; 1241 threadName: string = ''; 1242} 1243export class HeapStruct { 1244 startTime: number | undefined; 1245 endTime: number | undefined; 1246 dur: number | undefined; 1247 density: number | undefined; 1248 heapsize: number | undefined; 1249 maxHeapSize: number = 0; 1250 maxDensity: number = 0; 1251 minHeapSize: number = 0; 1252 minDensity: number = 0; 1253} 1254export class NativeEvent { 1255 startTime: number = 0; 1256 heapSize: number = 0; 1257 eventType: number = 0; 1258} 1259export class StatisticsSelection { 1260 memoryTap: string = ''; 1261 max: number = 0; 1262} 1263 1264class AnalysisSample { 1265 id: number; 1266 count: number; 1267 size: number; 1268 type: number; 1269 startTs: number; 1270 1271 isRelease: boolean; 1272 releaseCount?: number; 1273 releaseSize?: number; 1274 1275 endTs?: number; 1276 subType?: string; 1277 tid?: number; 1278 addr?: string; 1279 1280 libId!: number; 1281 libName!: string; 1282 symbolId!: number; 1283 symbolName!: string; 1284 1285 constructor(id: number, size: number, count: number, type: number | string, startTs: number) { 1286 this.id = id; 1287 this.size = size; 1288 this.count = count; 1289 this.startTs = startTs; 1290 switch (type) { 1291 case 'AllocEvent': 1292 case '0': 1293 this.type = 0; 1294 this.isRelease = false; 1295 break; 1296 case 'MmapEvent': 1297 case '1': 1298 this.isRelease = false; 1299 this.type = 1; 1300 break; 1301 case 'FreeEvent': 1302 this.isRelease = true; 1303 this.type = 2; 1304 break; 1305 case 'MunmapEvent': 1306 this.isRelease = true; 1307 this.type = 3; 1308 break; 1309 default: 1310 this.isRelease = false; 1311 this.type = -1; 1312 } 1313 } 1314} 1315