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