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 */ 15 16 17export class ChartStruct { 18 depth: number = 0; 19 symbol: string = ''; 20 lib: string = ''; 21 path: string = ''; 22 addr: string = ''; 23 size: number = 0; 24 count: number = 0; 25 eventCount: number = 0; 26 eventPercent: string = ''; 27 dur: number = 0; 28 parent: ChartStruct | undefined; 29 children: Array<ChartStruct> = []; 30 isSearch: boolean = false; 31 tsArray: Array<number> = []; // 每个绘制的函数由哪些时间点的样本组成 32 countArray: Array<number> = []; // native hook统计模式下一个时间点有多次分配 33 durArray: Array<number> = []; 34 isThread: boolean = false; 35 isProcess: boolean = false; 36 sourceFile: string = ''; 37 lineNumber: Set<number> = new Set<number>(); 38} 39 40export class Msg { 41 tag: string = ''; 42 index: number = 0; 43 isSending: boolean = false; 44 data: Array<unknown> = []; 45} 46 47export class HiPerfSymbol { 48 id: number = 0; 49 startTime: number = 0; 50 eventCount: number = 0; 51 endTime: number = 0; 52 totalTime: number = 0; 53 fileId: number = 0; 54 symbolId: number = 0; 55 cpu_id: number = 0; 56 depth: number = 0; 57 children?: Array<HiPerfSymbol>; 58 callchain_id: number = 0; 59 thread_id: number = 0; 60 name: string = ''; 61 62 public clone(): HiPerfSymbol { 63 const cloneSymbol = new HiPerfSymbol(); 64 cloneSymbol.children = []; 65 cloneSymbol.depth = this.depth; 66 return cloneSymbol; 67 } 68} 69 70export class MerageBean extends ChartStruct { 71 #parentNode: MerageBean | undefined = undefined; 72 #total = 0; 73 parent: MerageBean | undefined = undefined; 74 id: string = ''; 75 parentId: string = ''; 76 self?: string = '0s'; 77 weight?: string; 78 weightPercent?: string; 79 selfDur: number = 0; 80 dur: number = 0; 81 pid: number = 0; 82 canCharge: boolean = true; 83 isStore = 0; 84 isSelected: boolean = false; 85 searchShow: boolean = true; 86 children: MerageBean[] = []; 87 initChildren: MerageBean[] = []; 88 type: number = 0; 89 set parentNode(data: MerageBean | undefined) { 90 this.parent = data; 91 this.#parentNode = data; 92 } 93 94 get parentNode(): MerageBean | undefined { 95 return this.#parentNode; 96 } 97 98 set total(data: number) { 99 this.#total = data; 100 this.weight = `${getProbablyTime(this.dur)}`; 101 this.weightPercent = `${((this.dur / data) * 100).toFixed(1)}%`; 102 } 103 104 get total(): number { 105 return this.#total; 106 } 107} 108 109class MerageBeanDataSplit { 110 systmeRuleName = '/system/'; 111 numRuleName = '/max/min/'; 112 113 //所有的操作都是针对整个树结构的, 不区分特定的数据 114 splitTree( 115 splitMapData: unknown, 116 data: MerageBean[], 117 name: string, 118 isCharge: boolean, 119 isSymbol: boolean, 120 currentTreeList: ChartStruct[], 121 searchValue: string 122 ): void { 123 data.forEach((process) => { 124 process.children = []; 125 if (isCharge) { 126 this.recursionChargeInitTree(splitMapData, process, name, isSymbol); 127 } else { 128 this.recursionPruneInitTree(splitMapData, process, name, isSymbol); 129 } 130 }); 131 this.resetAllNode(data, currentTreeList, searchValue); 132 } 133 134 recursionChargeInitTree(splitMapData: unknown, node: MerageBean, symbolName: string, isSymbol: boolean): void { 135 if ((isSymbol && node.symbol === symbolName) || (!isSymbol && node.lib === symbolName)) { 136 //@ts-ignore 137 (splitMapData[symbolName] = splitMapData[symbolName] || []).push(node); 138 node.isStore++; 139 } 140 if (node.initChildren.length > 0) { 141 node.initChildren.forEach((child) => { 142 this.recursionChargeInitTree(splitMapData, child, symbolName, isSymbol); 143 }); 144 } 145 } 146 147 recursionPruneInitTree(splitMapData: unknown, node: MerageBean, symbolName: string, isSymbol: boolean): void { 148 if ((isSymbol && node.symbol === symbolName) || (!isSymbol && node.lib === symbolName)) { 149 //@ts-ignore 150 (splitMapData[symbolName] = splitMapData[symbolName] || []).push(node); 151 node.isStore++; 152 this.pruneChildren(splitMapData, node, symbolName); 153 } else if (node.initChildren.length > 0) { 154 node.initChildren.forEach((child) => { 155 this.recursionPruneInitTree(splitMapData, child, symbolName, isSymbol); 156 }); 157 } 158 } 159 160 //symbol lib prune 161 recursionPruneTree(node: MerageBean, symbolName: string, isSymbol: boolean): void { 162 if ((isSymbol && node.symbol === symbolName) || (!isSymbol && node.lib === symbolName)) { 163 node.parent && node.parent.children.splice(node.parent.children.indexOf(node), 1); 164 } else { 165 node.children.forEach((child) => { 166 this.recursionPruneTree(child, symbolName, isSymbol); 167 }); 168 } 169 } 170 171 recursionChargeByRule( 172 splitMapData: unknown, 173 node: MerageBean, 174 ruleName: string, 175 rule: (node: MerageBean) => boolean 176 ): void { 177 if (node.initChildren.length > 0) { 178 node.initChildren.forEach((child) => { 179 if (rule(child)) { 180 //@ts-ignore 181 (splitMapData[ruleName] = splitMapData[ruleName] || []).push(child); 182 child.isStore++; 183 } 184 this.recursionChargeByRule(splitMapData, child, ruleName, rule); 185 }); 186 } 187 } 188 189 pruneChildren(splitMapData: unknown, node: MerageBean, symbolName: string): void { 190 if (node.initChildren.length > 0) { 191 node.initChildren.forEach((child) => { 192 child.isStore++; 193 //@ts-ignore 194 (splitMapData[symbolName] = splitMapData[symbolName] || []).push(child); 195 this.pruneChildren(splitMapData, child, symbolName); 196 }); 197 } 198 } 199 200 hideSystemLibrary(allProcess: MerageBean[], splitMapData: unknown): void { 201 allProcess.forEach((item) => { 202 item.children = []; 203 this.recursionChargeByRule(splitMapData, item, this.systmeRuleName, (node) => { 204 return node.path.startsWith(this.systmeRuleName); 205 }); 206 }); 207 } 208 209 hideNumMaxAndMin(allProcess: MerageBean[], splitMapData: unknown, startNum: number, endNum: string): void { 210 let max = endNum === '∞' ? Number.POSITIVE_INFINITY : parseInt(endNum); 211 allProcess.forEach((item) => { 212 item.children = []; 213 this.recursionChargeByRule(splitMapData, item, this.numRuleName, (node) => { 214 return node.count < startNum || node.count > max; 215 }); 216 }); 217 } 218 219 resotreAllNode(splitMapData: unknown, symbols: string[]): void { 220 symbols.forEach((symbol) => { 221 //@ts-ignore 222 let list = splitMapData[symbol]; 223 if (list !== undefined) { 224 list.forEach((item: unknown) => { 225 //@ts-ignore 226 item.isStore--; 227 }); 228 } 229 }); 230 } 231 232 resetAllNode(data: MerageBean[], currentTreeList: ChartStruct[], searchValue: string): void { 233 // 去除全部节点上次筛选的标记 234 this.clearSearchNode(currentTreeList); 235 // 去除线程上次筛选的标记 236 data.forEach((process) => { 237 process.searchShow = true; 238 process.isSearch = false; 239 }); 240 // 恢复上次筛选 241 this.resetNewAllNode(data, currentTreeList); 242 if (searchValue !== '') { 243 // 将筛选匹配的节点做上标记,search = true,否则都是false 244 this.findSearchNode(data, searchValue, false); 245 // 将searchshow为true的节点整理树结构,其余的不管 246 this.resetNewAllNode(data, currentTreeList); 247 } 248 } 249 250 resetNewAllNode(data: MerageBean[], currentTreeList: ChartStruct[]): void { 251 data.forEach((process) => { 252 process.children = []; 253 }); 254 // 所有节点的children都置空 255 let values = currentTreeList.map((item: ChartStruct) => { 256 item.children = []; 257 return item; 258 }); 259 values.forEach((item: unknown) => { 260 //@ts-ignore 261 if (item.parentNode !== undefined) { 262 //@ts-ignore 263 if (item.isStore === 0 && item.searchShow) { 264 /* 265 拿到当前节点的父节点,如果它的父节点没有被搜索,则找到它父节点的父节点 266 */ 267 //@ts-ignore 268 let parentNode = item.parentNode; 269 while (parentNode !== undefined && !(parentNode.isStore === 0 && parentNode.searchShow) && parentNode.addr) { 270 parentNode = parentNode.parentNode; 271 } 272 if (parentNode) { 273 //@ts-ignore 274 item.currentTreeParentNode = parentNode; 275 parentNode.children.push(item); 276 } 277 } 278 } 279 }); 280 } 281 282 findSearchNode(data: MerageBean[], search: string, parentSearch: boolean): void { 283 search = search.toLocaleLowerCase(); 284 data.forEach((item) => { 285 if ((item.symbol && item.symbol.toLocaleLowerCase().includes(search)) || parentSearch) { 286 item.searchShow = true; 287 item.isSearch = item.symbol !== undefined && item.symbol.toLocaleLowerCase().includes(search); 288 let parentNode = item.parent; 289 while (parentNode && !parentNode.searchShow) { 290 parentNode.searchShow = true; 291 parentNode = parentNode.parent; 292 } 293 } else { 294 item.searchShow = false; 295 item.isSearch = false; 296 } 297 if (item.children.length > 0) { 298 this.findSearchNode(item.children, search, item.searchShow); 299 } 300 }); 301 } 302 303 clearSearchNode(currentTreeList: ChartStruct[]): void { 304 currentTreeList.forEach((node) => { 305 //@ts-ignore 306 node.searchShow = true; 307 node.isSearch = false; 308 }); 309 } 310 311 splitAllProcess(allProcess: unknown[], splitMapData: unknown, list: unknown): void { 312 //@ts-ignore 313 list.forEach((item: unknown) => { 314 allProcess.forEach((process) => { 315 //@ts-ignore 316 if (item.select === '0') { 317 //@ts-ignore 318 this.recursionChargeInitTree(splitMapData, process, item.name, item.type === 'symbol'); 319 } else { 320 //@ts-ignore 321 this.recursionPruneInitTree(splitMapData, process, item.name, item.type === 'symbol'); 322 } 323 }); 324 //@ts-ignore 325 if (!item.checked) { 326 //@ts-ignore 327 this.resotreAllNode(splitMapData, [item.name]); 328 } 329 }); 330 } 331} 332 333export let merageBeanDataSplit = new MerageBeanDataSplit(); 334 335export abstract class LogicHandler { 336 abstract handle(data: unknown): void; 337 queryData(eventId: string, queryName: string, sql: string, args: unknown): void { 338 self.postMessage({ 339 id: eventId, 340 type: queryName, 341 isQuery: true, 342 args: args, 343 sql: sql, 344 }); 345 } 346 347 abstract clearAll(): void; 348} 349 350let dec = new TextDecoder(); 351 352export let setFileName = (path: string): string => { 353 let fileName = ''; 354 if (path) { 355 let number = path.lastIndexOf('/'); 356 if (number > 0) { 357 fileName = path.substring(number + 1); 358 return fileName; 359 } 360 } 361 return path; 362}; 363 364let pagination = (page: number, pageSize: number, source: Array<unknown>): unknown[] => { 365 let offset = (page - 1) * pageSize; 366 return offset + pageSize >= source.length 367 ? source.slice(offset, source.length) 368 : source.slice(offset, offset + pageSize); 369}; 370 371const PAGE_SIZE: number = 50_0000; 372export let postMessage = (id: unknown, action: string, results: Array<unknown>, pageSize: number = PAGE_SIZE): void => { 373 if (results.length > pageSize) { 374 let pageCount = Math.ceil(results.length / pageSize); 375 for (let i = 1; i <= pageCount; i++) { 376 let tag = 'start'; 377 if (i === 1) { 378 tag = 'start'; 379 } else if (i === pageCount) { 380 tag = 'end'; 381 } else { 382 tag = 'sending'; 383 } 384 let msg = new Msg(); 385 msg.tag = tag; 386 msg.index = i; 387 msg.isSending = tag !== 'end'; 388 msg.data = pagination(i, PAGE_SIZE, results); 389 self.postMessage({ 390 id: id, 391 action: action, 392 isSending: msg.tag !== 'end', 393 results: msg, 394 }); 395 } 396 results.length = 0; 397 } else { 398 let msg = new Msg(); 399 msg.tag = 'end'; 400 msg.index = 0; 401 msg.isSending = false; 402 msg.data = results; 403 self.postMessage({ id: id, action: action, results: msg }); 404 results.length = 0; 405 } 406}; 407export let translateJsonString = (str: string): string => { 408 return str // .padding 409 .replace(/[\t|\r|\n]/g, '') 410 .replace(/\\/g, '\\\\'); 411}; 412 413export let convertJSON = (arrBuf: ArrayBuffer | Array<unknown>): unknown[] => { 414 if (arrBuf instanceof ArrayBuffer) { 415 let string = dec.decode(arrBuf); 416 let jsonArray = []; 417 string = string.substring(string.indexOf('\n') + 1); 418 if (!string) { 419 } else { 420 let parse; 421 let tansStr = translateJsonString(string); 422 try { 423 parse = JSON.parse(translateJsonString(string)); 424 } catch { 425 tansStr = tansStr.replace(/[^\x20-\x7E]/g, '?'); //匹配乱码字符,将其转换为? 426 parse = JSON.parse(tansStr); 427 } 428 let columns = parse.columns; 429 let values = parse.values; 430 for (let i = 0; i < values.length; i++) { 431 let object = {}; 432 for (let j = 0; j < columns.length; j++) { 433 //@ts-ignore 434 object[columns[j]] = values[i][j]; 435 } 436 jsonArray.push(object); 437 } 438 } 439 return jsonArray; 440 } else { 441 return arrBuf; 442 } 443}; 444 445export let getByteWithUnit = (bytes: number): string => { 446 if (bytes < 0) { 447 return '-' + getByteWithUnit(Math.abs(bytes)); 448 } 449 let currentBytes = bytes; 450 let kb1 = 1 << 10; 451 let mb = (1 << 10) << 10; 452 let gb = ((1 << 10) << 10) << 10; // 1 gb 453 let res = ''; 454 if (currentBytes > gb) { 455 res += (currentBytes / gb).toFixed(2) + ' GB'; 456 } else if (currentBytes > mb) { 457 res += (currentBytes / mb).toFixed(2) + ' MB'; 458 } else if (currentBytes > kb1) { 459 res += (currentBytes / kb1).toFixed(2) + ' KB'; 460 } else { 461 res += Math.round(currentBytes) + ' byte'; 462 } 463 return res; 464}; 465 466export let getTimeString = (ns: number): string => { 467 let currentNs = ns; 468 let hour1 = 3600_000_000_000; 469 let minute1 = 60_000_000_000; 470 let second1 = 1_000_000_000; 471 let millisecond1 = 1_000_000; 472 let microsecond1 = 1_000; 473 let res = ''; 474 if (currentNs >= hour1) { 475 res += Math.floor(currentNs / hour1) + 'h '; 476 currentNs = currentNs - Math.floor(currentNs / hour1) * hour1; 477 } 478 if (currentNs >= minute1) { 479 res += Math.floor(currentNs / minute1) + 'm '; 480 currentNs = currentNs - Math.floor(ns / minute1) * minute1; 481 } 482 if (currentNs >= second1) { 483 res += Math.floor(currentNs / second1) + 's '; 484 currentNs = currentNs - Math.floor(currentNs / second1) * second1; 485 } 486 if (currentNs >= millisecond1) { 487 res += Math.floor(currentNs / millisecond1) + 'ms '; 488 currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1; 489 } 490 if (currentNs >= microsecond1) { 491 res += Math.floor(currentNs / microsecond1) + 'μs '; 492 currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1; 493 } 494 if (currentNs > 0) { 495 res += currentNs + 'ns '; 496 } 497 if (res === '') { 498 res = ns + ''; 499 } 500 return res; 501}; 502 503export function getProbablyTime(ns: number): string { 504 let currentNs = ns; 505 let hour1 = 3600_000_000_000; 506 let minute1 = 60_000_000_000; 507 let second1 = 1_000_000_000; 508 let millisecond1 = 1_000_000; 509 let microsecond1 = 1_000; 510 let res = ''; 511 if (currentNs >= hour1) { 512 res += (currentNs / hour1).toFixed(2) + 'h '; 513 } else if (currentNs >= minute1) { 514 res += (currentNs / minute1).toFixed(2) + 'm '; 515 } else if (currentNs >= second1) { 516 res += (currentNs / second1).toFixed(2) + 's '; 517 } else if (currentNs >= millisecond1) { 518 res += (currentNs / millisecond1).toFixed(2) + 'ms '; 519 } else if (currentNs >= microsecond1) { 520 res += (currentNs / microsecond1).toFixed(2) + 'μs '; 521 } else if (currentNs > 0) { 522 res += currentNs.toFixed(0) + 'ns '; 523 } else if (res === '') { 524 res = ns + ''; 525 } 526 return res; 527} 528 529export function getThreadUsageProbablyTime(ns: number): string { 530 let currentNs = ns; 531 let microsecond1 = 1_000; 532 let res = ''; 533 if (currentNs > 0) { 534 res += (currentNs / microsecond1).toFixed(2); 535 } else if (res === '') { 536 res = ns + ''; 537 } 538 return res; 539} 540 541export function timeMsFormat2p(timeNs: number): string { 542 let currentNs = timeNs; 543 let oneHour = 3600_000; 544 let oneMinute1 = 60_000; 545 let oneSecond = 1_000; // 1 second 546 let commonResult = ''; 547 if (currentNs >= oneHour) { 548 commonResult += Math.floor(currentNs / oneHour).toFixed(2) + 'h'; 549 return commonResult; 550 } 551 if (currentNs >= oneMinute1) { 552 commonResult += Math.floor(currentNs / oneMinute1).toFixed(2) + 'min'; 553 return commonResult; 554 } 555 if (currentNs >= oneSecond) { 556 commonResult += Math.floor(currentNs / oneSecond).toFixed(2) + 's'; 557 return commonResult; 558 } 559 if (currentNs > 0) { 560 commonResult += currentNs.toFixed(2) + 'ms'; 561 return commonResult; 562 } 563 if (commonResult === '') { 564 commonResult = '0s'; 565 } 566 return commonResult; 567} 568 569export function formatRealDate(date: Date, fmt: string): string { 570 let obj = { 571 'M+': date.getMonth() + 1, 572 'd+': date.getDate(), 573 'h+': date.getHours(), 574 'm+': date.getMinutes(), 575 's+': date.getSeconds(), 576 'q+': Math.floor((date.getMonth() + 3) / 3), 577 S: date.getMilliseconds(), 578 }; 579 if (/(y+)/.test(fmt)) { 580 fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 581 } 582 for (let key in obj) { 583 if (new RegExp('(' + key + ')').test(fmt)) { 584 // @ts-ignore 585 fmt = fmt.replace( 586 RegExp.$1, 587 // @ts-ignore 588 RegExp.$1.length === 1 ? obj[key] : ('00' + obj[key]).substr(('' + obj[key]).length) 589 ); 590 } 591 } 592 return fmt; 593} 594 595export function formatRealDateMs(timeNs: number): string { 596 return formatRealDate(new Date(timeNs / 1000000), 'MM-dd hh:mm:ss.S'); 597} 598 599export class JsProfilerSymbol { 600 id: number = 0; 601 nameId: number = 0; 602 name: string = ''; 603 scriptId: number = 0; 604 urlId: number = 0; 605 url: string = ''; 606 line: number = 0; 607 column: number = 0; 608 hitCount: number = 0; 609 childrenString?: string; 610 childrenIds: Array<number> = []; 611 children?: Array<JsProfilerSymbol>; 612 parentId: number = 0; 613 depth: number = -1; 614 cpuProfilerData?: JsProfilerSymbol; 615 616 public clone(): JsProfilerSymbol { 617 const cloneSymbol = new JsProfilerSymbol(); 618 cloneSymbol.name = this.name; 619 cloneSymbol.url = this.url; 620 cloneSymbol.hitCount = this.hitCount; 621 cloneSymbol.children = []; 622 cloneSymbol.childrenIds = []; 623 cloneSymbol.parentId = this.parentId; 624 cloneSymbol.depth = this.depth; 625 cloneSymbol.cpuProfilerData = this.cpuProfilerData; 626 return cloneSymbol; 627 } 628} 629 630export class HeapTreeDataBean { 631 MoudleName: string | undefined; 632 AllocationFunction: string | undefined; 633 symbolId: number = 0; 634 fileId: number = 0; 635 startTs: number = 0; 636 endTs: number = 0; 637 eventType: string | undefined; 638 depth: number = 0; 639 heapSize: number = 0; 640 eventId: number = 0; 641 addr: string = ''; 642 callChinId: number = 0; 643} 644 645export class PerfCall { 646 sampleId: number = 0; 647 depth: number = 0; 648 name: string = ''; 649} 650 651export class FileCallChain { 652 callChainId: number = 0; 653 depth: number = 0; 654 symbolsId: number = 0; 655 pathId: number = 0; 656 ip: string = ''; 657 isThread: boolean = false; 658} 659 660export class DataCache { 661 public static instance: DataCache | undefined; 662 public dataDict = new Map<number, string>(); 663 public eBpfCallChainsMap = new Map<number, Array<FileCallChain>>(); 664 public nmFileDict = new Map<number, string>(); 665 public nmHeapFrameMap = new Map<number, Array<HeapTreeDataBean>>(); 666 public perfCountToMs = 1; // 1000 / freq 667 public perfCallChainMap: Map<number, PerfCall> = new Map<number, PerfCall>(); 668 public jsCallChain: Array<JsProfilerSymbol> | undefined; 669 public jsSymbolMap = new Map<number, JsProfilerSymbol>(); 670 671 public static getInstance(): DataCache { 672 if (!this.instance) { 673 this.instance = new DataCache(); 674 } 675 return this.instance; 676 } 677 678 public clearAll(): void { 679 if (this.dataDict) { 680 this.dataDict.clear(); 681 } 682 this.clearEBpf(); 683 this.clearNM(); 684 this.clearPerf(); 685 this.clearJsCache(); 686 } 687 688 public clearNM(): void { 689 this.nmFileDict.clear(); 690 this.nmHeapFrameMap.clear(); 691 } 692 693 public clearEBpf(): void { 694 this.eBpfCallChainsMap.clear(); 695 } 696 697 public clearJsCache(): void { 698 if (this.jsCallChain) { 699 this.jsCallChain.length = 0; 700 } 701 this.jsSymbolMap.clear(); 702 } 703 704 public clearPerf(): void { 705 this.perfCallChainMap.clear(); 706 } 707} 708 709export class InitAnalysis { 710 public static instance: InitAnalysis | undefined; 711 public isInitAnalysis: boolean = true; 712 public static getInstance(): InitAnalysis { 713 if (!this.instance) { 714 this.instance = new InitAnalysis(); 715 } 716 return this.instance; 717 } 718} 719 720interface perfAsyncList { 721 tid?: number; 722 pid?: number; 723 time?: number; 724 symbol?: string; 725 traceid?: string; 726 eventCount?: number; 727 sampleCount?: number; 728 jsFuncName?: string; 729 callerCallchainid?: number; 730 calleeCallchainid?: number; 731 asyncFuncName?: string; 732 eventType?: string; 733 children?: Array<perfAsyncList>; 734 eventTypeId?: number; 735 symbolName?: string; 736 callerCallStack?: Array<perfAsyncList>; 737 calleeCallStack?: Array<perfAsyncList>; 738 callStackList?: Array<perfAsyncList>; 739 parent?: perfAsyncList; 740 isProcess?: boolean; 741 isThread?: boolean; 742 depth?: number; 743 isSearch?: boolean; 744 isJsStack?: boolean; 745 lib?: string; 746 isChartSelectParent?: boolean; 747 isChartSelect?: boolean; 748 isDraw?: boolean; 749 drawDur?: number; 750 drawEventCount?: number; 751 drawCount?: number; 752 drawSize?: number; 753 searchEventCount?: number; 754 searchCount?: number; 755 searchDur?: number; 756 searchSize?: number; 757 size?: number; 758 count?: number; 759 dur?: number; 760 tsArray?: Array<number>; 761 isCharged?: boolean; 762 addr?: string; 763} 764 765export function dealAsyncData( 766 arr: Array<perfAsyncList>, 767 perfCallChain: object, 768 nmCallChain: Map<number, Array<{ addr: string, depth: number, eventId: number, fileId: number, symbolId: number }>>, 769 dataDict: Map<number, string>, 770 searchValue: string 771): Array<perfAsyncList> { 772 // 转换为小写字符 773 searchValue = searchValue.toLocaleLowerCase(); 774 // 循环遍历每一条数据 775 for (let i = 0; i < arr.length; i++) { 776 let flag: boolean = false; 777 // 定义每条数据的调用栈与被调用栈数组 778 arr[i].calleeCallStack! = []; 779 arr[i].callerCallStack! = []; 780 // 从前端缓存的perfcallchain表与native_hook_frame表中拿到calleeId与callerId对应的数据 781 // @ts-ignore 782 let calleeCallChain = perfCallChain[arr[i].calleeCallchainid]; 783 let callerCallChain = nmCallChain.get(arr[i].callerCallchainid!)!; 784 // 循环被调用栈数组,拿到该条采样数据对应的所有被调用栈信息 785 for (let j = 0; j < calleeCallChain.length; j++) { 786 let calleeStack: perfAsyncList = {}; 787 // 拿到每一层被调用栈栈名 788 calleeStack.symbolName = dataDict.get(calleeCallChain[j].name)!; 789 // 判断该条采样数据的被调用栈链中是否包含用户筛选字段 790 if (calleeStack.symbolName.toLocaleLowerCase().indexOf(searchValue) !== -1) { 791 flag = true; 792 } 793 // 获取calleeCallchainid、depth、eventTypeId、lib、addr 794 calleeStack.calleeCallchainid = arr[i].calleeCallchainid!; 795 calleeStack.depth = calleeCallChain[j].depth; 796 calleeStack.eventTypeId = arr[i].eventTypeId!; 797 calleeStack.lib = calleeCallChain[j].fileName; 798 calleeStack.addr = `${'0x'}${calleeCallChain[j].vaddrInFile.toString(16)}`; 799 // 填充到该条数据的被调用栈数组中 800 arr[i].calleeCallStack!.push(calleeStack); 801 } 802 for (let z = 0; z < callerCallChain.length; z++) { 803 let callerStack: perfAsyncList = {}; 804 // 拿到每一层被调用栈栈名 805 callerStack.symbolName = dataDict.get(callerCallChain[z].symbolId)!; 806 // 判断该条采样数据的调用栈链中是否包含用户筛选字段 807 if (callerStack.symbolName.toLocaleLowerCase().indexOf(searchValue) !== -1) { 808 flag = true; 809 } 810 // 获取callerCallchainid、depth、eventTypeId、lib、addr 811 callerStack.callerCallchainid = arr[i].callerCallchainid!; 812 callerStack.depth = callerCallChain[z].depth; 813 callerStack.eventTypeId = arr[i].eventTypeId!; 814 callerStack.addr = callerCallChain[z].addr; 815 callerStack.lib = setFileName(dataDict.get(callerCallChain[z].fileId)!); 816 // 填充到该条数据的调用栈数组中 817 arr[i].callerCallStack!.push(callerStack); 818 } 819 // 若存在用户筛选字段内容,数据进行保留。若不存在,则在返回给前端的数据中删除此条数据,减少前端处理的数据量 820 if (!flag) { 821 arr.splice(i, 1); 822 i--; 823 } 824 } 825 return arr; 826}