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