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 let callChains = this.createThreadSample(nativeHookSample); 796 let topIndex = isTopDown ? 0 : callChains.length - 1; 797 if (callChains.length > 0) { 798 let key = ''; 799 if (this.isHideThread) { 800 key = (callChains[topIndex].symbolId || '') + '-' + (callChains[topIndex].fileId || ''); 801 } else { 802 key = 803 nativeHookSample.tid + 804 '-' + 805 (callChains[topIndex].symbolId || '') + 806 '-' + 807 (callChains[topIndex].fileId || ''); 808 } 809 let root = this.currentTreeMapData[key]; 810 if (root === undefined) { 811 root = new NativeHookCallInfo(); 812 root.threadName = nativeHookSample.threadName; 813 this.currentTreeMapData[key] = root; 814 this.currentTreeList.push(root); 815 } 816 this.mergeCallChainSample(root, callChains[topIndex], nativeHookSample); 817 if (callChains.length > 1) { 818 this.merageChildrenByIndex(root, callChains, topIndex, nativeHookSample, isTopDown); 819 } 820 } 821 }); 822 let rootMerageMap = this.mergeNodeData(totalCount, totalSize); 823 this.handleCurrentTreeList(totalCount, totalSize); 824 this.allThreads = Object.values(rootMerageMap) as NativeHookCallInfo[]; 825 } 826 private mergeNodeData(totalCount: number, totalSize: number): CallInfoMap { 827 let rootMerageMap: CallInfoMap = {}; 828 let threads = Object.values(this.currentTreeMapData); 829 threads.forEach((merageData: NativeHookCallInfo): void => { 830 if (this.isHideThread) { 831 merageData.tid = 0; 832 merageData.threadName = undefined; 833 } 834 if (rootMerageMap[merageData.tid] === undefined) { 835 let threadMerageData = new NativeHookCallInfo(); //新增进程的节点数据 836 threadMerageData.canCharge = false; 837 threadMerageData.type = -1; 838 threadMerageData.isThread = true; 839 threadMerageData.symbol = `${merageData.threadName || 'Thread'} [${merageData.tid}]`; 840 threadMerageData.children.push(merageData); 841 threadMerageData.initChildren.push(merageData); 842 threadMerageData.count = merageData.count || 1; 843 threadMerageData.heapSize = merageData.heapSize; 844 threadMerageData.totalCount = totalCount; 845 threadMerageData.totalSize = totalSize; 846 threadMerageData.tsArray = [...merageData.tsArray]; 847 threadMerageData.countArray = [...merageData.countArray]; 848 rootMerageMap[merageData.tid] = threadMerageData; 849 } else { 850 rootMerageMap[merageData.tid].children.push(merageData); 851 rootMerageMap[merageData.tid].initChildren.push(merageData); 852 rootMerageMap[merageData.tid].count += merageData.count || 1; 853 rootMerageMap[merageData.tid].heapSize += merageData.heapSize; 854 rootMerageMap[merageData.tid].totalCount = totalCount; 855 rootMerageMap[merageData.tid].totalSize = totalSize; 856 for (const count of merageData.countArray) { 857 rootMerageMap[merageData.tid].countArray.push(count); 858 } 859 for (const ts of merageData.tsArray) { 860 rootMerageMap[merageData.tid].tsArray.push(ts); 861 } 862 } 863 merageData.parentNode = rootMerageMap[merageData.tid]; //子节点添加父节点的引用 864 }); 865 return rootMerageMap; 866 } 867 private handleCurrentTreeList(totalCount: number, totalSize: number): void { 868 let id = 0; 869 this.currentTreeList.forEach((nmTreeNode: NativeHookCallInfo): void => { 870 nmTreeNode.totalCount = totalCount; 871 nmTreeNode.totalSize = totalSize; 872 this.setMerageName(nmTreeNode); 873 if (nmTreeNode.id === '') { 874 nmTreeNode.id = id + ''; 875 id++; 876 } 877 if (nmTreeNode.parentNode) { 878 if (nmTreeNode.parentNode.id === '') { 879 nmTreeNode.parentNode.id = id + ''; 880 id++; 881 } 882 nmTreeNode.parentId = nmTreeNode.parentNode.id; 883 } 884 }); 885 } 886 private groupCallChainSample(paramMap: Map<string, unknown>): void { 887 let filterAllocType = paramMap.get('filterAllocType') as string; 888 let filterEventType = paramMap.get('filterEventType') as string; 889 let filterResponseType = paramMap.get('filterResponseType') as number; 890 let filterAnalysis = paramMap.get('filterByTitleArr') as FilterByAnalysis; 891 if (filterAnalysis) { 892 if (filterAnalysis.type) { 893 filterEventType = filterAnalysis.type; 894 } 895 if (filterAnalysis.libId) { 896 filterResponseType = filterAnalysis.libId; 897 } 898 } 899 let libTree = paramMap?.get('filterExpression') as NativeMemoryExpression; 900 let leftNs = paramMap.get('leftNs') as number; 901 let rightNs = paramMap.get('rightNs') as number; 902 let nativeHookType = paramMap.get('nativeHookType') as string; 903 let statisticsSelection = paramMap.get('statisticsSelection') as StatisticsSelection[]; 904 if (!libTree && filterAllocType === '0' && filterEventType === '0' && filterResponseType === -1) { 905 this.currentSamples = this.queryAllCallchainsSamples; 906 return; 907 } 908 this.useFreedSize = this.isStatisticMode && filterAllocType === '2'; 909 let filter = this.dataFilter( 910 libTree, 911 filterAnalysis, 912 filterAllocType, 913 leftNs, 914 rightNs, 915 nativeHookType, 916 filterResponseType, 917 filterEventType, 918 statisticsSelection 919 ); 920 let groupMap = this.setGroupMap(filter, filterAllocType, nativeHookType); 921 this.currentSamples = Object.values(groupMap); 922 } 923 private dataFilter( 924 libTree: NativeMemoryExpression, 925 filterAnalysis: FilterByAnalysis, 926 filterAllocType: string, 927 leftNs: number, 928 rightNs: number, 929 nativeHookType: string, 930 filterResponseType: number, 931 filterEventType: string, 932 statisticsSelection: StatisticsSelection[] 933 ): NativeHookStatistics[] { 934 return this.queryAllCallchainsSamples.filter((item: NativeHookStatistics): boolean => { 935 let filterAllocation = true; 936 if (nativeHookType === 'native-hook') { 937 filterAllocation = this.setFilterAllocation(item, filterAllocType, filterAllocation, leftNs, rightNs); 938 } else { 939 if (filterAllocType === '1') { 940 filterAllocation = item.heapSize > item.freeSize; 941 } else if (filterAllocType === '2') { 942 filterAllocation = item.freeSize > 0; 943 } 944 } 945 let filterThread = true; 946 if (filterAnalysis && filterAnalysis.tid) { 947 filterThread = item.tid === filterAnalysis.tid; 948 } 949 let filterLastLib = true; 950 if (libTree) { 951 filterLastLib = this.filterExpressionSample(item, libTree); 952 this.searchValue = ''; 953 } else { 954 filterLastLib = filterResponseType === -1 ? true : filterResponseType === item.lastLibId; 955 } 956 let filterFunction = true; 957 if (filterAnalysis && filterAnalysis.symbolId) { 958 filterFunction = filterAnalysis.symbolId === item.lastSymbolId; 959 } 960 let filterNative = this.getTypeFromIndex(parseInt(filterEventType), item, statisticsSelection); 961 return filterAllocation && filterNative && filterLastLib && filterThread && filterFunction; 962 }); 963 } 964 private setFilterAllocation( 965 item: NativeHookStatistics, 966 filterAllocType: string, 967 filterAllocation: boolean, 968 leftNs: number, 969 rightNs: number 970 ): boolean { 971 if (filterAllocType === '1') { 972 filterAllocation = 973 item.startTs >= leftNs && 974 item.startTs <= rightNs && 975 (item.endTs > rightNs || item.endTs === 0 || item.endTs === null); 976 } else if (filterAllocType === '2') { 977 filterAllocation = 978 item.startTs >= leftNs && 979 item.startTs <= rightNs && 980 item.endTs <= rightNs && 981 item.endTs !== 0 && 982 item.endTs !== null; 983 } 984 return filterAllocation; 985 } 986 private setGroupMap( 987 filter: Array<NativeHookStatistics>, 988 filterAllocType: string, 989 nativeHookType: string 990 ): StatisticMap { 991 let groupMap: StatisticMap = {}; 992 filter.forEach((sample: NativeHookStatistics): void => { 993 let currentNode = groupMap[sample.tid + '-' + sample.eventId] || new NativeHookStatistics(); 994 if (currentNode.count === 0) { 995 Object.assign(currentNode, sample); 996 if (filterAllocType === '1' && nativeHookType !== 'native-hook') { 997 currentNode.heapSize = sample.heapSize - sample.freeSize; 998 currentNode.count = sample.count - sample.freeCount; 999 } 1000 if (currentNode.count === 0) { 1001 currentNode.count++; 1002 currentNode.countArray.push(1); 1003 currentNode.tsArray.push(sample.startTs); 1004 } 1005 } else { 1006 currentNode.count++; 1007 currentNode.heapSize += sample.heapSize; 1008 currentNode.countArray.push(1); 1009 currentNode.tsArray.push(sample.startTs); 1010 } 1011 groupMap[sample.tid + '-' + sample.eventId] = currentNode; 1012 }); 1013 return groupMap; 1014 } 1015 1016 private filterExpressionSample(sample: NativeHookStatistics, expressStruct: NativeMemoryExpression): boolean { 1017 const itemLibName = this.dataCache.dataDict.get(sample.lastLibId); 1018 const itemSymbolName = this.dataCache.dataDict.get(sample.lastSymbolId); 1019 if (!itemLibName || !itemSymbolName) { 1020 return false; 1021 } 1022 1023 function isMatch(libTree: Map<string, string[]>, match: boolean): boolean { 1024 for (const [lib, symbols] of libTree) { 1025 // lib不包含则跳过 1026 if (!itemLibName!.toLowerCase().includes(lib.toLowerCase()) && lib !== '*') { 1027 continue; 1028 } 1029 // * 表示全量 1030 if (symbols.includes('*')) { 1031 match = true; 1032 break; 1033 } 1034 1035 for (const symbol of symbols) { 1036 // 匹配到了就返回 1037 if (itemSymbolName!.toLowerCase().includes(symbol.toLowerCase())) { 1038 match = true; 1039 break; 1040 } 1041 } 1042 // 如果匹配到了,跳出循环 1043 if (match) { 1044 break; 1045 } 1046 //全部没有匹配到 1047 match = false; 1048 } 1049 return match; 1050 } 1051 1052 let includeMatch = expressStruct.includeLib.size === 0; // true表达这条数据需要显示 1053 includeMatch = isMatch(expressStruct.includeLib, includeMatch); 1054 1055 if (expressStruct.abandonLib.size === 0) { 1056 return includeMatch; 1057 } 1058 1059 let abandonMatch = false; // false表示这条数据需要显示 1060 abandonMatch = isMatch(expressStruct.abandonLib, abandonMatch); 1061 1062 return includeMatch && !abandonMatch; 1063 } 1064 1065 createThreadSample(sample: NativeHookStatistics): HeapTreeDataBean[] { 1066 return this.dataCache.nmHeapFrameMap.get(sample.eventId) || []; 1067 } 1068 1069 mergeCallChainSample( 1070 currentNode: NativeHookCallInfo, 1071 callChain: HeapTreeDataBean, 1072 sample: NativeHookStatistics 1073 ): void { 1074 if (currentNode.symbol === undefined || currentNode.symbol === '') { 1075 currentNode.symbol = callChain.AllocationFunction || ''; 1076 currentNode.addr = callChain.addr; 1077 currentNode.eventId = sample.eventId; 1078 currentNode.eventType = sample.eventType; 1079 currentNode.symbolId = callChain.symbolId; 1080 currentNode.fileId = callChain.fileId; 1081 currentNode.tid = sample.tid; 1082 } 1083 if (this.useFreedSize) { 1084 currentNode.count += sample.freeCount || 1; 1085 } else { 1086 currentNode.count += sample.count || 1; 1087 } 1088 1089 if (sample.countArray && sample.countArray.length > 0) { 1090 currentNode.countArray = currentNode.countArray.concat(sample.countArray); 1091 } else { 1092 currentNode.countArray.push(sample.count); 1093 } 1094 1095 if (sample.tsArray && sample.tsArray.length > 0) { 1096 currentNode.tsArray = currentNode.tsArray.concat(sample.tsArray); 1097 } else { 1098 currentNode.tsArray.push(sample.startTs); 1099 } 1100 if (this.useFreedSize) { 1101 currentNode.heapSize += sample.freeSize; 1102 } else { 1103 currentNode.heapSize += sample.heapSize; 1104 } 1105 } 1106 1107 merageChildrenByIndex( 1108 currentNode: NativeHookCallInfo, 1109 callChainDataList: HeapTreeDataBean[], 1110 index: number, 1111 sample: NativeHookStatistics, 1112 isTopDown: boolean 1113 ): void { 1114 isTopDown ? index++ : index--; 1115 let isEnd = isTopDown ? callChainDataList.length === index + 1 : index === 0; 1116 let node: NativeHookCallInfo; 1117 if ( 1118 //@ts-ignore 1119 currentNode.initChildren.filter((child: NativeHookCallInfo): boolean => { 1120 if ( 1121 child.symbolId === callChainDataList[index]?.symbolId && 1122 child.fileId === callChainDataList[index]?.fileId 1123 ) { 1124 node = child; 1125 this.mergeCallChainSample(child, callChainDataList[index], sample); 1126 return true; 1127 } 1128 return false; 1129 }).length === 0 1130 ) { 1131 node = new NativeHookCallInfo(); 1132 this.mergeCallChainSample(node, callChainDataList[index], sample); 1133 currentNode.children.push(node); 1134 currentNode.initChildren.push(node); 1135 this.currentTreeList.push(node); 1136 node.parentNode = currentNode; 1137 } 1138 if (node! && !isEnd) { 1139 this.merageChildrenByIndex(node, callChainDataList, index, sample, isTopDown); 1140 } 1141 } 1142 1143 private extractSymbolAndPath(node: NativeHookCallInfo, str?: string): void { 1144 node.symbol = 'unknown'; 1145 if (!str) { 1146 return; 1147 } 1148 const match = str.match(/^([^\[:]+):\[url:(.+)\]$/); 1149 if (!match) { 1150 return; 1151 } 1152 node.symbol = match[1].trim(); 1153 node.lib = match[2].replace(/^url:/, ''); 1154 } 1155 1156 private isHap(path: string): boolean { 1157 for (const name of HAP_TYPE) { 1158 if (path.endsWith(name)) { 1159 return true; 1160 } 1161 } 1162 return false; 1163 } 1164 1165 setMerageName(currentNode: NativeHookCallInfo): void { 1166 currentNode.lib = this.dataCache.dataDict.get(currentNode.fileId) || 'unknown'; 1167 if (this.isHap(currentNode.lib)) { 1168 const fullName = this.dataCache.dataDict.get(currentNode.symbolId); 1169 this.extractSymbolAndPath(currentNode, fullName); 1170 } else { 1171 currentNode.symbol = 1172 this.groupCutFilePath(currentNode.symbolId, this.dataCache.dataDict.get(currentNode.symbolId) || '') ?? 1173 'unknown'; 1174 } 1175 currentNode.path = currentNode.lib; 1176 currentNode.lib = setFileName(currentNode.lib); 1177 currentNode.symbol = `${currentNode.symbol} (${currentNode.lib})`; 1178 currentNode.type = 1179 currentNode.lib.endsWith('.so.1') || currentNode.lib.endsWith('.dll') || currentNode.lib.endsWith('.so') ? 0 : 1; 1180 } 1181 clearSplitMapData(symbolName: string): void { 1182 if (symbolName in this.splitMapData) { 1183 Reflect.deleteProperty(this.splitMapData, symbolName); 1184 } 1185 } 1186 resolvingNMCallAction(params: unknown[]): NativeHookCallInfo[] { 1187 if (params.length > 0) { 1188 params.forEach((item: unknown): void => { 1189 //@ts-ignore 1190 let funcName = item.funcName; 1191 //@ts-ignore 1192 let args = item.funcArgs; 1193 if (funcName && args) { 1194 this.handleDataByFuncName(funcName, args); 1195 } 1196 }); 1197 } 1198 return this.allThreads.filter((thread: NativeHookCallInfo): boolean => { 1199 return thread.children && thread.children.length > 0; 1200 }); 1201 } 1202 handleDataByFuncName(funcName: string, args: unknown[]): void { 1203 switch (funcName) { 1204 case 'hideThread': 1205 this.isHideThread = args[0] as boolean; 1206 break; 1207 case 'groupCallchainSample': 1208 this.groupCallChainSample(args[0] as Map<string, unknown>); 1209 break; 1210 case 'getCallChainsBySampleIds': 1211 this.freshCurrentCallchains(this.currentSamples, args[0] as boolean); 1212 break; 1213 case 'hideSystemLibrary': 1214 merageBeanDataSplit.hideSystemLibrary(this.allThreads, this.splitMapData); 1215 break; 1216 case 'hideNumMaxAndMin': 1217 merageBeanDataSplit.hideNumMaxAndMin(this.allThreads, this.splitMapData, args[0] as number, args[1] as string); 1218 break; 1219 case 'splitAllProcess': 1220 merageBeanDataSplit.splitAllProcess(this.allThreads, this.splitMapData, args[0]); 1221 break; 1222 case 'resetAllNode': 1223 merageBeanDataSplit.resetAllNode(this.allThreads, this.currentTreeList, this.searchValue); 1224 break; 1225 case 'resotreAllNode': 1226 merageBeanDataSplit.resotreAllNode(this.splitMapData, args[0] as string[]); 1227 break; 1228 case 'splitTree': 1229 merageBeanDataSplit.splitTree( 1230 this.splitMapData, 1231 this.allThreads, 1232 args[0] as string, 1233 args[1] as boolean, 1234 args[2] as boolean, 1235 this.currentTreeList, 1236 this.searchValue 1237 ); 1238 break; 1239 case 'setSearchValue': 1240 this.searchValue = args[0] as string; 1241 break; 1242 case 'clearSplitMapData': 1243 this.clearSplitMapData(args[0] as string); 1244 break; 1245 } 1246 } 1247} 1248 1249export class NativeHookStatistics { 1250 id: number = 0; 1251 eventId: number = 0; 1252 eventType: string = ''; 1253 subType: string = ''; 1254 subTypeId: number = 0; 1255 heapSize: number = 0; 1256 freeSize: number = 0; 1257 addr: string = ''; 1258 startTs: number = 0; 1259 endTs: number = 0; 1260 sumHeapSize: number = 0; 1261 max: number = 0; 1262 count: number = 0; 1263 freeCount: number = 0; 1264 tid: number = 0; 1265 threadName: string = ''; 1266 lastLibId: number = 0; 1267 lastSymbolId: number = 0; 1268 isSelected: boolean = false; 1269 tsArray: Array<number> = []; 1270 countArray: Array<number> = []; 1271} 1272export class NativeHookCallInfo extends MerageBean { 1273 #totalCount: number = 0; 1274 #totalSize: number = 0; 1275 symbolId: number = 0; 1276 fileId: number = 0; 1277 count: number = 0; 1278 countValue: string = ''; 1279 countPercent: string = ''; 1280 type: number = 0; 1281 heapSize: number = 0; 1282 heapPercent: string = ''; 1283 heapSizeStr: string = ''; 1284 eventId: number = 0; 1285 tid: number = 0; 1286 threadName: string | undefined = ''; 1287 eventType: string = ''; 1288 isSelected: boolean = false; 1289 set totalCount(total: number) { 1290 this.#totalCount = total; 1291 this.countValue = `${this.count}`; 1292 this.size = this.heapSize; 1293 this.countPercent = `${((this.count / total) * 100).toFixed(1)}%`; 1294 } 1295 get totalCount(): number { 1296 return this.#totalCount; 1297 } 1298 set totalSize(total: number) { 1299 this.#totalSize = total; 1300 this.heapSizeStr = `${getByteWithUnit(this.heapSize)}`; 1301 this.heapPercent = `${((this.heapSize / total) * 100).toFixed(1)}%`; 1302 } 1303 get totalSize(): number { 1304 return this.#totalSize; 1305 } 1306} 1307export class NativeMemory { 1308 index: number = 0; 1309 eventId: number = 0; 1310 eventType: string = ''; 1311 subType: string = ''; 1312 subTypeId: number = 0; 1313 addr: string = ''; 1314 startTs: number = 0; 1315 endTs: number = 0; 1316 heapSize: number = 0; 1317 symbol: string = ''; 1318 library: string = ''; 1319 lastLibId: number = 0; 1320 lastSymbolId: number = 0; 1321 isSelected: boolean = false; 1322 threadId: number = 0; 1323} 1324 1325export class HeapStruct { 1326 startTime: number | undefined; 1327 endTime: number | undefined; 1328 dur: number | undefined; 1329 density: number | undefined; 1330 heapsize: number | undefined; 1331 maxHeapSize: number = 0; 1332 maxDensity: number = 0; 1333 minHeapSize: number = 0; 1334 minDensity: number = 0; 1335} 1336export class StatisticsSelection { 1337 memoryTap: string = ''; 1338 max: number = 0; 1339} 1340 1341class AnalysisSample { 1342 id: number; 1343 count: number; 1344 size: number; 1345 type: number; 1346 startTs: number; 1347 1348 isRelease: boolean; 1349 releaseCount?: number; 1350 releaseSize?: number; 1351 1352 endTs?: number; 1353 subType?: string; 1354 tid?: number; 1355 threadName?: string; 1356 addr?: string; 1357 1358 libId!: number; 1359 libName!: string; 1360 symbolId!: number; 1361 symbolName!: string; 1362 1363 constructor(id: number, size: number, count: number, type: number | string, startTs: number) { 1364 this.id = id; 1365 this.size = size; 1366 this.count = count; 1367 this.startTs = startTs; 1368 switch (type) { 1369 case 'AllocEvent': 1370 case '0': 1371 this.type = 0; 1372 this.isRelease = false; 1373 break; 1374 case 'MmapEvent': 1375 case '1': 1376 this.isRelease = false; 1377 this.type = 1; 1378 break; 1379 case 'FreeEvent': 1380 this.isRelease = true; 1381 this.type = 2; 1382 break; 1383 case 'MunmapEvent': 1384 this.isRelease = true; 1385 this.type = 3; 1386 break; 1387 default: 1388 this.isRelease = false; 1389 this.type = -1; 1390 } 1391 } 1392} 1393