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 16export class ChartStruct { 17 depth: number = 0; 18 symbol: string = ''; 19 lib: string = ''; 20 addr: string = ''; 21 size: number = 0; 22 count: number = 0; 23 eventCount: number = 0; 24 eventPercent: string = ''; 25 dur: number = 0; 26 parent: ChartStruct | undefined; 27 children: Array<ChartStruct> = []; 28 isSearch: boolean = false; 29 tsArray: Array<number> = []; // 每个绘制的函数由哪些时间点的样本组成 30 countArray: Array<number> = []; // native hook统计模式下一个时间点有多次分配 31 durArray: Array<number> = []; 32 isThread: boolean = false; 33 isProcess: boolean = false; 34} 35 36export class Msg { 37 tag: string = ''; 38 index: number = 0; 39 isSending: boolean = false; 40 data: Array<any> = []; 41} 42 43export class HiPerfSymbol { 44 id: number = 0; 45 startTime: number = 0; 46 eventCount: number = 0; 47 endTime: number = 0; 48 totalTime: number = 0; 49 fileId: number = 0; 50 symbolId: number = 0; 51 cpu_id: number = 0; 52 depth: number = 0; 53 children?: Array<HiPerfSymbol>; 54 callchain_id: number = 0; 55 thread_id: number = 0; 56 name: string = ''; 57 58 public clone(): HiPerfSymbol { 59 const cloneSymbol = new HiPerfSymbol(); 60 cloneSymbol.children = new Array<HiPerfSymbol>(); 61 cloneSymbol.depth = this.depth; 62 return cloneSymbol; 63 } 64} 65 66export class MerageBean extends ChartStruct { 67 #parentNode: MerageBean | undefined = undefined; 68 #total = 0; 69 parent: MerageBean | undefined = undefined; 70 id: string = ''; 71 parentId: string = ''; 72 symbolName: string = ''; 73 symbol: string = ''; 74 libName: string = ''; 75 path: 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: any, 116 data: MerageBean[], 117 name: string, 118 isCharge: boolean, 119 isSymbol: boolean, 120 currentTreeList: any[], 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: any, node: MerageBean, symbolName: string, isSymbol: boolean): void { 135 if ((isSymbol && node.symbolName == symbolName) || (!isSymbol && node.libName == symbolName)) { 136 (splitMapData[symbolName] = splitMapData[symbolName] || []).push(node); 137 node.isStore++; 138 } 139 if (node.initChildren.length > 0) { 140 node.initChildren.forEach((child) => { 141 this.recursionChargeInitTree(splitMapData, child, symbolName, isSymbol); 142 }); 143 } 144 } 145 146 recursionPruneInitTree(splitMapData: any, node: MerageBean, symbolName: string, isSymbol: boolean): void { 147 if ((isSymbol && node.symbolName == symbolName) || (!isSymbol && node.libName == symbolName)) { 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.symbolName == symbolName) || (!isSymbol && node.libName == 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: any, 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 (splitMapData[ruleName] = splitMapData[ruleName] || []).push(child); 179 child.isStore++; 180 } 181 this.recursionChargeByRule(splitMapData, child, ruleName, rule); 182 }); 183 } 184 } 185 186 pruneChildren(splitMapData: any, node: MerageBean, symbolName: string): void { 187 if (node.initChildren.length > 0) { 188 node.initChildren.forEach((child) => { 189 child.isStore++; 190 (splitMapData[symbolName] = splitMapData[symbolName] || []).push(child); 191 this.pruneChildren(splitMapData, child, symbolName); 192 }); 193 } 194 } 195 196 hideSystemLibrary(allProcess: MerageBean[], splitMapData: any): void { 197 allProcess.forEach((item) => { 198 item.children = []; 199 this.recursionChargeByRule(splitMapData, item, this.systmeRuleName, (node) => { 200 return node.path.startsWith(this.systmeRuleName); 201 }); 202 }); 203 } 204 205 hideNumMaxAndMin(allProcess: MerageBean[], splitMapData: any, startNum: number, endNum: string): void { 206 let max = endNum == '∞' ? Number.POSITIVE_INFINITY : parseInt(endNum); 207 allProcess.forEach((item) => { 208 item.children = []; 209 this.recursionChargeByRule(splitMapData, item, this.numRuleName, (node) => { 210 return node.count < startNum || node.count > max; 211 }); 212 }); 213 } 214 215 resotreAllNode(splitMapData: any, symbols: string[]): void { 216 symbols.forEach((symbol) => { 217 let list = splitMapData[symbol]; 218 if (list != undefined) { 219 list.forEach((item: any) => { 220 item.isStore--; 221 }); 222 } 223 }); 224 } 225 226 resetAllNode(data: MerageBean[], currentTreeList: any[], searchValue: string): void { 227 this.clearSearchNode(currentTreeList); 228 data.forEach((process) => { 229 process.searchShow = true; 230 process.isSearch = false; 231 }); 232 this.resetNewAllNode(data, currentTreeList); 233 if (searchValue != '') { 234 this.findSearchNode(data, searchValue, false); 235 this.resetNewAllNode(data, currentTreeList); 236 } 237 } 238 239 resetNewAllNode(data: MerageBean[], currentTreeList: any[]): void { 240 data.forEach((process) => { 241 process.children = []; 242 }); 243 let values = currentTreeList.map((item: any) => { 244 item.children = []; 245 return item; 246 }); 247 values.forEach((item: any) => { 248 if (item.parentNode != undefined) { 249 if (item.isStore == 0 && item.searchShow) { 250 let parentNode = item.parentNode; 251 while (parentNode != undefined && !(parentNode.isStore == 0 && parentNode.searchShow)) { 252 parentNode = parentNode.parentNode; 253 } 254 if (parentNode) { 255 item.currentTreeParentNode = parentNode; 256 parentNode.children.push(item); 257 } 258 } 259 } 260 }); 261 } 262 263 findSearchNode(data: MerageBean[], search: string, parentSearch: boolean): void { 264 search = search.toLocaleLowerCase(); 265 data.forEach((item) => { 266 if ((item.symbolName && item.symbolName.toLocaleLowerCase().includes(search)) || parentSearch) { 267 item.searchShow = true; 268 item.isSearch = item.symbolName != undefined && item.symbolName.toLocaleLowerCase().includes(search); 269 let parentNode = item.parent; 270 while (parentNode && !parentNode.searchShow) { 271 parentNode.searchShow = true; 272 parentNode = parentNode.parent; 273 } 274 } else { 275 item.searchShow = false; 276 item.isSearch = false; 277 } 278 if (item.children.length > 0) { 279 this.findSearchNode(item.children, search, item.searchShow); 280 } 281 }); 282 } 283 284 clearSearchNode(currentTreeList: any[]): void { 285 currentTreeList.forEach((node) => { 286 node.searchShow = true; 287 node.isSearch = false; 288 }); 289 } 290 291 splitAllProcess(allProcess: any[], splitMapData: any, list: any[]): void { 292 list.forEach((item: any) => { 293 allProcess.forEach((process) => { 294 if (item.select == '0') { 295 this.recursionChargeInitTree(splitMapData, process, item.name, item.type == 'symbol'); 296 } else { 297 this.recursionPruneInitTree(splitMapData, process, item.name, item.type == 'symbol'); 298 } 299 }); 300 if (!item.checked) { 301 this.resotreAllNode(splitMapData, [item.name]); 302 } 303 }); 304 } 305} 306 307export let merageBeanDataSplit = new MerageBeanDataSplit(); 308 309export abstract class LogicHandler { 310 abstract handle(data: any): void; 311 queryData(eventId: string, queryName: string, sql: string, args: any): void { 312 self.postMessage({ 313 id: eventId, 314 type: queryName, 315 isQuery: true, 316 args: args, 317 sql: sql, 318 }); 319 } 320 321 abstract clearAll(): void; 322} 323 324let dec = new TextDecoder(); 325 326export let setFileName = (path: string): string => { 327 let fileName = ''; 328 if (path) { 329 let number = path.lastIndexOf('/'); 330 if (number > 0) { 331 fileName = path.substring(number + 1); 332 return fileName; 333 } 334 } 335 return path; 336}; 337 338let pagination = (page: number, pageSize: number, source: Array<any>): any[] => { 339 let offset = (page - 1) * pageSize; 340 return offset + pageSize >= source.length 341 ? source.slice(offset, source.length) 342 : source.slice(offset, offset + pageSize); 343}; 344 345const PAGE_SIZE: number = 50_0000; 346export let postMessage = (id: any, action: string, results: Array<any>, pageSize: number = PAGE_SIZE): void => { 347 if (results.length > pageSize) { 348 let pageCount = Math.ceil(results.length / pageSize); 349 for (let i = 1; i <= pageCount; i++) { 350 let tag = 'start'; 351 if (i == 1) { 352 tag = 'start'; 353 } else if (i == pageCount) { 354 tag = 'end'; 355 } else { 356 tag = 'sending'; 357 } 358 let msg = new Msg(); 359 msg.tag = tag; 360 msg.index = i; 361 msg.isSending = tag != 'end'; 362 msg.data = pagination(i, PAGE_SIZE, results); 363 self.postMessage({ 364 id: id, 365 action: action, 366 isSending: msg.tag != 'end', 367 results: msg, 368 }); 369 } 370 results.length = 0; 371 } else { 372 let msg = new Msg(); 373 msg.tag = 'end'; 374 msg.index = 0; 375 msg.isSending = false; 376 msg.data = results; 377 self.postMessage({ id: id, action: action, results: msg }); 378 results.length = 0; 379 } 380}; 381export let translateJsonString = (str: string): string => { 382 return str // .padding 383 .replace(/[\t|\r|\n]/g, '') 384 .replace(/\\/g, '\\\\'); 385}; 386 387export let convertJSON = (arrBuf: ArrayBuffer | Array<any>): any[] => { 388 if (arrBuf instanceof ArrayBuffer) { 389 let string = dec.decode(arrBuf); 390 let jsonArray = []; 391 string = string.substring(string.indexOf('\n') + 1); 392 if (!string) { 393 } else { 394 let parse; 395 let tansStr = translateJsonString(string); 396 try { 397 parse = JSON.parse(translateJsonString(string)); 398 } catch { 399 tansStr = tansStr.replace(/[^\x20-\x7E]/g, '?'); //匹配乱码字符,将其转换为? 400 parse = JSON.parse(tansStr); 401 } 402 let columns = parse.columns; 403 let values = parse.values; 404 for (let i = 0; i < values.length; i++) { 405 let object: any = {}; 406 for (let j = 0; j < columns.length; j++) { 407 object[columns[j]] = values[i][j]; 408 } 409 jsonArray.push(object); 410 } 411 } 412 return jsonArray; 413 } else { 414 return arrBuf; 415 } 416}; 417 418export let getByteWithUnit = (bytes: number): string => { 419 if (bytes < 0) { 420 return '-' + getByteWithUnit(Math.abs(bytes)); 421 } 422 let currentBytes = bytes; 423 let kb1 = 1 << 10; 424 let mb = (1 << 10) << 10; 425 let gb = ((1 << 10) << 10) << 10; // 1 gb 426 let res = ''; 427 if (currentBytes > gb) { 428 res += (currentBytes / gb).toFixed(2) + ' Gb'; 429 } else if (currentBytes > mb) { 430 res += (currentBytes / mb).toFixed(2) + ' Mb'; 431 } else if (currentBytes > kb1) { 432 res += (currentBytes / kb1).toFixed(2) + ' Kb'; 433 } else { 434 res += Math.round(currentBytes) + ' byte'; 435 } 436 return res; 437}; 438 439export let getTimeString = (ns: number): string => { 440 let currentNs = ns; 441 let hour1 = 3600_000_000_000; 442 let minute1 = 60_000_000_000; 443 let second1 = 1_000_000_000; 444 let millisecond1 = 1_000_000; 445 let microsecond1 = 1_000; 446 let res = ''; 447 if (currentNs >= hour1) { 448 res += Math.floor(currentNs / hour1) + 'h '; 449 currentNs = currentNs - Math.floor(currentNs / hour1) * hour1; 450 } 451 if (currentNs >= minute1) { 452 res += Math.floor(currentNs / minute1) + 'm '; 453 currentNs = currentNs - Math.floor(ns / minute1) * minute1; 454 } 455 if (currentNs >= second1) { 456 res += Math.floor(currentNs / second1) + 's '; 457 currentNs = currentNs - Math.floor(currentNs / second1) * second1; 458 } 459 if (currentNs >= millisecond1) { 460 res += Math.floor(currentNs / millisecond1) + 'ms '; 461 currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1; 462 } 463 if (currentNs >= microsecond1) { 464 res += Math.floor(currentNs / microsecond1) + 'μs '; 465 currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1; 466 } 467 if (currentNs > 0) { 468 res += currentNs + 'ns '; 469 } 470 if (res == '') { 471 res = ns + ''; 472 } 473 return res; 474}; 475 476export function getProbablyTime(ns: number): string { 477 let currentNs = ns; 478 let hour1 = 3600_000_000_000; 479 let minute1 = 60_000_000_000; 480 let second1 = 1_000_000_000; 481 let millisecond1 = 1_000_000; 482 let microsecond1 = 1_000; 483 let res = ''; 484 if (currentNs >= hour1) { 485 res += (currentNs / hour1).toFixed(2) + 'h '; 486 } else if (currentNs >= minute1) { 487 res += (currentNs / minute1).toFixed(2) + 'm '; 488 } else if (currentNs >= second1) { 489 res += (currentNs / second1).toFixed(2) + 's '; 490 } else if (currentNs >= millisecond1) { 491 res += (currentNs / millisecond1).toFixed(2) + 'ms '; 492 } else if (currentNs >= microsecond1) { 493 res += (currentNs / microsecond1).toFixed(2) + 'μs '; 494 } else if (currentNs > 0) { 495 res += currentNs.toFixed(0) + 'ns '; 496 } else if (res == '') { 497 res = ns + ''; 498 } 499 return res; 500} 501 502export function timeMsFormat2p(timeNs: number): string { 503 let currentNs = timeNs; 504 let oneHour = 3600_000; 505 let oneMinute1 = 60_000; 506 let oneSecond = 1_000; // 1 second 507 let commonResult = ''; 508 if (currentNs >= oneHour) { 509 commonResult += Math.floor(currentNs / oneHour).toFixed(2) + 'h'; 510 return commonResult; 511 } 512 if (currentNs >= oneMinute1) { 513 commonResult += Math.floor(currentNs / oneMinute1).toFixed(2) + 'min'; 514 return commonResult; 515 } 516 if (currentNs >= oneSecond) { 517 commonResult += Math.floor(currentNs / oneSecond).toFixed(2) + 's'; 518 return commonResult; 519 } 520 if (currentNs > 0) { 521 commonResult += currentNs.toFixed(2) + 'ms'; 522 return commonResult; 523 } 524 if (commonResult === '') { 525 commonResult = '0s'; 526 } 527 return commonResult; 528} 529 530export function formatRealDate(date: Date, fmt: string): string { 531 let obj = { 532 'M+': date.getMonth() + 1, 533 'd+': date.getDate(), 534 'h+': date.getHours(), 535 'm+': date.getMinutes(), 536 's+': date.getSeconds(), 537 'q+': Math.floor((date.getMonth() + 3) / 3), 538 S: date.getMilliseconds(), 539 }; 540 if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 541 for (let key in obj) { 542 if (new RegExp('(' + key + ')').test(fmt)) { 543 // @ts-ignore 544 fmt = fmt.replace( 545 RegExp.$1, 546 // @ts-ignore 547 RegExp.$1.length == 1 ? obj[key] : ('00' + obj[key]).substr(('' + obj[key]).length) 548 ); 549 } 550 } 551 return fmt; 552} 553 554export function formatRealDateMs(timeNs: number): string { 555 return formatRealDate(new Date(timeNs / 1000000), 'MM-dd hh:mm:ss.S'); 556} 557 558export class JsProfilerSymbol { 559 id: number = 0; 560 nameId: number = 0; 561 name: string = ''; 562 scriptId: number = 0; 563 urlId: number = 0; 564 url: string = ''; 565 line: number = 0; 566 column: number = 0; 567 hitCount: number = 0; 568 childrenString?: string; 569 childrenIds: Array<number> = []; 570 children?: Array<JsProfilerSymbol>; 571 parentId: number = 0; 572 depth: number = -1; 573 cpuProfilerData?: JsProfilerSymbol; 574 575 public clone(): JsProfilerSymbol { 576 const cloneSymbol = new JsProfilerSymbol(); 577 cloneSymbol.name = this.name; 578 cloneSymbol.url = this.url; 579 cloneSymbol.hitCount = this.hitCount; 580 cloneSymbol.children = new Array<JsProfilerSymbol>(); 581 cloneSymbol.childrenIds = new Array<number>(); 582 cloneSymbol.parentId = this.parentId; 583 cloneSymbol.depth = this.depth; 584 cloneSymbol.cpuProfilerData = this.cpuProfilerData; 585 return cloneSymbol; 586 } 587} 588 589export class HeapTreeDataBean { 590 MoudleName: string | undefined; 591 AllocationFunction: string | undefined; 592 symbolId: number = 0; 593 fileId: number = 0; 594 startTs: number = 0; 595 endTs: number = 0; 596 eventType: string | undefined; 597 depth: number = 0; 598 heapSize: number = 0; 599 eventId: number = 0; 600 addr: string = ''; 601 callChinId: number = 0; 602} 603 604export class PerfCall { 605 sampleId: number = 0; 606 depth: number = 0; 607 name: string = ''; 608} 609 610export class FileCallChain { 611 callChainId: number = 0; 612 depth: number = 0; 613 symbolsId: number = 0; 614 pathId: number = 0; 615 ip: string = ''; 616 isThread: boolean = false; 617} 618 619export class DataCache { 620 public static instance: DataCache | undefined; 621 public dataDict = new Map<number, string>(); 622 public eBpfCallChainsMap = new Map<number, Array<FileCallChain>>(); 623 public nmFileDict = new Map<number, string>(); 624 public nmHeapFrameMap = new Map<number, Array<HeapTreeDataBean>>(); 625 public perfCountToMs = 1; // 1000 / freq 626 public perfCallChainMap: Map<number, PerfCall> = new Map<number, PerfCall>(); 627 public jsCallChain: Array<JsProfilerSymbol> | undefined; 628 public jsSymbolMap = new Map<number, JsProfilerSymbol>(); 629 630 public static getInstance(): DataCache { 631 if (!this.instance) { 632 this.instance = new DataCache(); 633 } 634 return this.instance; 635 } 636 637 public clearAll(): void { 638 if (this.dataDict) { 639 this.dataDict.clear(); 640 } 641 this.clearEBpf(); 642 this.clearNM(); 643 this.clearPerf(); 644 this.clearJsCache(); 645 } 646 647 public clearNM(): void { 648 this.nmFileDict.clear(); 649 this.nmHeapFrameMap.clear(); 650 } 651 652 public clearEBpf(): void { 653 this.eBpfCallChainsMap.clear(); 654 } 655 656 public clearJsCache(): void { 657 if (this.jsCallChain) { 658 this.jsCallChain.length = 0; 659 } 660 this.jsSymbolMap.clear(); 661 } 662 663 public clearPerf(): void { 664 this.perfCallChainMap.clear(); 665 } 666} 667 668export class InitAnalysis { 669 public static instance: InitAnalysis | undefined; 670 public isInitAnalysis: boolean = true; 671 public static getInstance(): InitAnalysis { 672 if (!this.instance) { 673 this.instance = new InitAnalysis(); 674 } 675 return this.instance; 676 } 677} 678