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