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