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 16import { 17 LogicHandler, 18 ChartStruct, 19 convertJSON, 20 DataCache, 21 HiPerfSymbol, 22 PerfCall, 23} from './ProcedureLogicWorkerCommon'; 24import { PerfBottomUpStruct } from '../../bean/PerfBottomUpStruct'; 25import { SelectionParam } from '../../bean/BoxSelection'; 26import { dealAsyncData } from './ProcedureLogicWorkerCommon'; 27 28const systemRuleName: string = '/system/'; 29const numRuleName: string = '/max/min/'; 30const maxDepth: number = 256; 31 32type PerfThreadMap = { 33 [pid: string]: PerfThread; 34}; 35type PerfCallChainMap = { 36 [id: number]: PerfCallChain[]; 37}; 38type FileMap = { 39 [id: number]: PerfFile[]; 40}; 41 42type MergeMap = { 43 [id: string]: PerfCallChainMerageData; 44}; 45 46type spiltMap = { 47 [id: string]: PerfCallChainMerageData[]; 48}; 49 50export class ProcedureLogicWorkerPerf extends LogicHandler { 51 filesData: FileMap = {}; 52 samplesData: Array<PerfCountSample> = []; 53 threadData: PerfThreadMap = {}; 54 callChainData: PerfCallChainMap = {}; 55 splitMapData: spiltMap = {}; 56 currentTreeMapData: MergeMap = {}; 57 currentTreeList: PerfCallChainMerageData[] = []; 58 searchValue: string = ''; 59 dataSource: PerfCallChainMerageData[] = []; 60 allProcess: PerfCallChainMerageData[] = []; 61 currentEventId: string = ''; 62 isAnalysis: boolean = false; 63 isPerfBottomUp: boolean = false; 64 isHideThread: boolean = false; 65 isHideThreadState: boolean = false; 66 isOnlyKernel: boolean = false; 67 private lib: object | undefined; 68 private symbol: object | undefined; 69 private dataCache = DataCache.getInstance(); 70 private isTopDown: boolean = true; 71 // 应对当depth为0是的结构变化后的数据还原 72 private forkAllProcess: PerfCallChainMerageData[] = []; 73 private lineMap: Map<string, Set<number>> = new Map<string, Set<number>>(); 74 75 handle(data: unknown): void { 76 //@ts-ignore 77 this.currentEventId = data.id; 78 //@ts-ignore 79 if (data && data.type) { 80 //@ts-ignore 81 switch (data.type) { 82 case 'perf-init': 83 //@ts-ignore 84 this.dataCache.perfCountToMs = data.params.fValue; 85 this.initPerfFiles(); 86 break; 87 case 'perf-queryPerfFiles': 88 //@ts-ignore 89 this.perfQueryPerfFiles(data.params.list); 90 break; 91 case 'perf-queryPerfThread': 92 //@ts-ignore 93 this.perfQueryPerfThread(data.params.list); 94 break; 95 case 'perf-queryPerfCalls': 96 //@ts-ignore 97 this.perfQueryPerfCalls(data.params.list); 98 break; 99 case 'perf-queryPerfCallchains': 100 this.perfQueryPerfCallchains(data); 101 break; 102 case 'perf-analysis': 103 this.getAnalysisData(data); 104 break; 105 case 'perf-bottomUp': 106 this.getBottomUpData(data); 107 break; 108 case 'perf-profile': 109 this.getProfileData(data); 110 break; 111 case 'perf-action': 112 this.perfAction(data); 113 break; 114 case 'perf-vaddr-back': 115 this.rebackVaddrList(data); 116 break; 117 case 'perf-vaddr': 118 this.perfGetVaddr(data); 119 break; 120 case 'perf-reset': 121 this.perfReset(); 122 break; 123 case 'perf-async': 124 this.perfAsync(data); 125 break; 126 } 127 } 128 } 129 130 private getAnalysisData(data: unknown): void { 131 //@ts-ignore 132 this.samplesData = convertJSON(data.params.list) || []; 133 let result; 134 result = this.resolvingAction([ 135 { 136 funcName: 'combineAnalysisCallChain', 137 funcArgs: [true], 138 }, 139 ]), 140 self.postMessage({ 141 //@ts-ignore 142 id: data.id, 143 //@ts-ignore 144 action: data.action, 145 // @ts-ignore 146 results: result, 147 }); 148 } 149 150 private getBottomUpData(data: unknown): void { 151 //@ts-ignore 152 this.samplesData = convertJSON(data.params.list) || []; 153 let result; 154 result = this.resolvingAction([ 155 { 156 funcName: 'getBottomUp', 157 funcArgs: [true], 158 }, 159 ]); 160 self.postMessage({ 161 //@ts-ignore 162 id: data.id, 163 //@ts-ignore 164 action: data.action, 165 // @ts-ignore 166 results: result, 167 }); 168 } 169 170 private getProfileData(data: unknown): void { 171 //@ts-ignore 172 this.samplesData = convertJSON(data.params.list) || []; 173 let result; 174 if (this.lib) { 175 let libData = this.combineCallChainForAnalysis(this.lib); 176 this.freshPerfCallchains(libData, this.isTopDown); 177 result = this.allProcess; 178 this.lib = undefined; 179 } else if (this.symbol) { 180 let funData = this.combineCallChainForAnalysis(this.symbol); 181 this.freshPerfCallchains(funData, this.isTopDown); 182 result = this.allProcess; 183 this.symbol = undefined; 184 } else { 185 result = this.resolvingAction([ 186 { 187 funcName: 'getCallChainsBySampleIds', 188 funcArgs: [this.isTopDown], 189 }, 190 ]); 191 } 192 self.postMessage({ 193 //@ts-ignore 194 id: data.id, 195 //@ts-ignore 196 action: data.action, 197 // @ts-ignore 198 results: result, 199 }); 200 } 201 202 private perfQueryPerfFiles(list: Array<PerfFile>): void { 203 let files = convertJSON(list) || []; 204 //@ts-ignore 205 files.forEach((file: PerfFile) => { 206 this.filesData[file.fileId] = this.filesData[file.fileId] || []; 207 PerfFile.setFileName(file); 208 this.filesData[file.fileId].push(file); 209 }); 210 this.initPerfThreads(); 211 } 212 private perfQueryPerfThread(list: Array<PerfThread>): void { 213 let threads = convertJSON(list) || []; 214 //@ts-ignore 215 threads.forEach((thread: PerfThread): void => { 216 this.threadData[thread.tid] = thread; 217 }); 218 this.initPerfCalls(); 219 } 220 private perfQueryPerfCalls(list: Array<PerfCall>): void { 221 let perfCalls = convertJSON(list) || []; 222 if (perfCalls.length !== 0) { 223 //@ts-ignore 224 perfCalls.forEach((perfCall: PerfCall): void => { 225 this.dataCache.perfCallChainMap.set(perfCall.sampleId, perfCall); 226 }); 227 } 228 this.initPerfCallchains(); 229 } 230 private perfQueryPerfCallchains(data: unknown): void { 231 //@ts-ignore 232 let arr = convertJSON(data.params.list) || []; 233 this.initPerfCallChainTopDown(arr as PerfCallChain[]); 234 235 self.postMessage({ 236 // @ts-ignore 237 id: data.id, 238 // @ts-ignore 239 action: data.action, 240 results: this.dataCache.perfCallChainMap, 241 }); 242 } 243 private perfQueryCallchainsGroupSample(data: unknown): void { 244 //@ts-ignore 245 this.samplesData = convertJSON(data.params.list) || []; 246 let result; 247 if (this.isAnalysis) { 248 result = this.resolvingAction([ 249 { 250 funcName: 'combineAnalysisCallChain', 251 funcArgs: [true], 252 }, 253 ]); 254 } else if (this.isPerfBottomUp) { 255 result = this.resolvingAction([ 256 { 257 funcName: 'getBottomUp', 258 funcArgs: [true], 259 }, 260 ]); 261 } else { 262 if (this.lib) { 263 let libData = this.combineCallChainForAnalysis(this.lib); 264 this.freshPerfCallchains(libData, this.isTopDown); 265 result = this.allProcess; 266 this.lib = undefined; 267 } else if (this.symbol) { 268 let funData = this.combineCallChainForAnalysis(this.symbol); 269 this.freshPerfCallchains(funData, this.isTopDown); 270 result = this.allProcess; 271 this.symbol = undefined; 272 } else { 273 result = this.resolvingAction([ 274 { 275 funcName: 'getCallChainsBySampleIds', 276 funcArgs: [this.isTopDown], 277 }, 278 ]); 279 } 280 } 281 self.postMessage({ 282 //@ts-ignore 283 id: data.id, 284 //@ts-ignore 285 action: data.action, 286 results: result, 287 }); 288 if (this.isAnalysis) { 289 this.isAnalysis = false; 290 } 291 } 292 293 rebackVaddrList(data: unknown): void { 294 // @ts-ignore 295 let vaddrCallchainList = convertJSON(data.params.list); 296 let sampleCallChainList: unknown = []; 297 for (let i = 0; i < vaddrCallchainList.length; i++) { 298 let funcVaddrLastItem = {}; 299 // @ts-ignore 300 let callChains = [...this.callChainData[vaddrCallchainList[i].callchain_id]]; 301 const lastCallChain = callChains[callChains.length - 1]; 302 // @ts-ignore 303 funcVaddrLastItem.callchain_id = lastCallChain.sampleId; 304 // @ts-ignore 305 funcVaddrLastItem.symbolName = this.dataCache.dataDict.get(lastCallChain.name as number); 306 // @ts-ignore 307 funcVaddrLastItem.vaddrInFile = lastCallChain.vaddrInFile; 308 // @ts-ignore 309 funcVaddrLastItem.offsetToVaddr = lastCallChain.offsetToVaddr; 310 // @ts-ignore 311 funcVaddrLastItem.process_id = vaddrCallchainList[i].process_id; 312 // @ts-ignore 313 funcVaddrLastItem.thread_id = vaddrCallchainList[i].thread_id; 314 // @ts-ignore 315 funcVaddrLastItem.count = vaddrCallchainList[i].count; 316 // @ts-ignore 317 funcVaddrLastItem.libName = lastCallChain.fileName; 318 // @ts-ignore 319 sampleCallChainList.push(funcVaddrLastItem); 320 } 321 322 self.postMessage({ 323 //@ts-ignore 324 id: data.id, 325 //@ts-ignore 326 action: data.action, 327 results: sampleCallChainList, 328 }); 329 } 330 331 private perfAction(data: unknown): void { 332 //@ts-ignore 333 const params = data.params; 334 if (params) { 335 let filter = params.filter((item: { funcName: string }): boolean => item.funcName === 'getCurrentDataFromDbBottomUp' || 336 item.funcName === 'getCurrentDataFromDbProfile' || item.funcName === 'getCurrentDataFromDbAnalysis'); 337 let libFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'showLibLevelData'); 338 let funFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'showFunLevelData'); 339 if (libFilter.length !== 0) { 340 this.setLib(libFilter); 341 } 342 if (funFilter.length !== 0) { 343 this.setSymbol(funFilter); 344 } 345 if (filter.length === 0) { 346 let result = this.calReturnData(params); 347 self.postMessage({ 348 //@ts-ignore 349 id: data.id, 350 //@ts-ignore 351 action: data.action, 352 results: result, 353 }); 354 } else { 355 this.resolvingAction(params); 356 } 357 } 358 } 359 360 private perfGetVaddr(data: unknown): void { 361 // @ts-ignore 362 const params = data.params; 363 this.backVaddrData(data); 364 } 365 366 backVaddrData(data: unknown): void { 367 // @ts-ignore 368 this.handleDataByFuncName(data.params[0].funcName, data.params[0].funcArgs); 369 } 370 371 private perfReset(): void { 372 this.isHideThread = false; 373 this.isHideThreadState = false; 374 this.isTopDown = true; 375 this.isOnlyKernel = false; 376 } 377 378 private perfAsync(data: unknown): void { 379 //@ts-ignore 380 if (data.params.list) { 381 // 若前端存储过调用栈信息与被调用栈信息,可考虑从此处一起返回给主线程 382 //@ts-ignore 383 let arr = convertJSON(data.params.list) || []; 384 //@ts-ignore 385 let result = dealAsyncData(arr, this.callChainData, this.dataCache.nmHeapFrameMap, this.dataCache.dataDict, this.searchValue); 386 this.searchValue = ''; 387 self.postMessage({ 388 //@ts-ignore 389 id: data.id, 390 //@ts-ignore 391 action: data.action, 392 results: result, 393 }); 394 arr = []; 395 result = []; 396 } else { 397 //@ts-ignore 398 this.searchValue = data.params.searchValue.toLocaleLowerCase(); 399 //@ts-ignore 400 this.queryPerfAsync(data.params); 401 } 402 } 403 404 private setLib(libFilter: unknown): void { 405 this.lib = { 406 //@ts-ignore 407 libId: libFilter[0].funcArgs[0], 408 //@ts-ignore 409 libName: libFilter[0].funcArgs[1], 410 }; 411 } 412 private setSymbol(funFilter: unknown): void { 413 this.symbol = { 414 //@ts-ignore 415 symbolId: funFilter[0].funcArgs[0], 416 //@ts-ignore 417 symbolName: funFilter[0].funcArgs[1], 418 }; 419 } 420 private calReturnData(params: unknown): Array<unknown> { 421 let result: unknown[]; 422 //@ts-ignore 423 let callChainsFilter = params.filter( 424 (item: { funcName: string }): boolean => item.funcName === 'getCallChainsBySampleIds' 425 ); 426 callChainsFilter.length > 0 ? (this.isTopDown = callChainsFilter[0].funcArgs[0]) : (this.isTopDown = true); 427 //@ts-ignore 428 let isHideSystemSoFilter = params.filter( 429 (item: { funcName: string }): boolean => item.funcName === 'hideSystemLibrary' 430 ); 431 //@ts-ignore 432 let hideThreadFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'hideThread'); 433 //@ts-ignore 434 let hideThreadStateFilter = params.filter( 435 (item: { funcName: string }): boolean => item.funcName === 'hideThreadState' 436 ); 437 //@ts-ignore 438 let onlyKernelFilter = [true]; 439 if (this.lib) { 440 if ( 441 callChainsFilter.length > 0 || 442 isHideSystemSoFilter.length > 0 || 443 hideThreadFilter.length > 0 || 444 hideThreadStateFilter.length > 0 || 445 onlyKernelFilter.length > 0 446 ) { 447 this.samplesData = this.combineCallChainForAnalysis(this.lib); 448 //@ts-ignore 449 result = this.resolvingAction(params); 450 } else { 451 let libData = this.combineCallChainForAnalysis(this.lib); 452 this.freshPerfCallchains(libData, this.isTopDown); 453 result = this.allProcess; 454 this.lib = undefined; 455 } 456 } else if (this.symbol) { 457 if ( 458 callChainsFilter.length > 0 || 459 isHideSystemSoFilter.length > 0 || 460 hideThreadFilter.length > 0 || 461 hideThreadStateFilter.length > 0 || 462 onlyKernelFilter.length > 0 463 ) { 464 this.samplesData = this.combineCallChainForAnalysis(this.symbol); 465 //@ts-ignore 466 result = this.resolvingAction(params); 467 } else { 468 let funData = this.combineCallChainForAnalysis(this.symbol); 469 this.freshPerfCallchains(funData, this.isTopDown); 470 result = this.allProcess; 471 this.symbol = undefined; 472 } 473 } else { 474 //@ts-ignore 475 result = this.resolvingAction(params); 476 } 477 return result; 478 } 479 initPerfFiles(): void { 480 this.clearAll(); 481 this.queryData( 482 this.currentEventId, 483 'perf-queryPerfFiles', 484 `select file_id as fileId, symbol, path 485 from perf_files`, 486 {} 487 ); 488 } 489 490 initPerfThreads(): void { 491 this.queryData( 492 this.currentEventId, 493 'perf-queryPerfThread', 494 `select a.thread_id as tid, a.thread_name as threadName, a.process_id as pid, b.thread_name as processName 495 from perf_thread a 496 left join (select * from perf_thread where thread_id = process_id) b on a.process_id = b.thread_id`, 497 {} 498 ); 499 } 500 501 initPerfCalls(): void { 502 this.queryData( 503 this.currentEventId, 504 'perf-queryPerfCalls', 505 `select count(callchain_id) as depth, callchain_id as sampleId, name 506 from perf_callchain 507 where callchain_id != -1 508 group by callchain_id`, 509 {} 510 ); 511 } 512 513 initPerfCallchains(): void { 514 this.queryData( 515 this.currentEventId, 516 'perf-queryPerfCallchains', 517 `select c.name, 518 c.callchain_id as sampleId, 519 c.vaddr_in_file as vaddrInFile, 520 c.offset_to_vaddr as offsetToVaddr, 521 c.file_id as fileId, 522 c.depth, 523 c.symbol_id as symbolId, 524 c.source_file_id as sourceFileId, 525 c.line_number as lineNumber 526 from perf_callchain c 527 where callchain_id != -1;`, 528 {} 529 ); 530 } 531 532 queryPerfAsync(args: unknown): void { 533 let str: string = ``; 534 //@ts-ignore 535 if (args.cpu.length > 0) { 536 //@ts-ignore 537 str += `or cpu_id in (${args.cpu.join(',')})`; 538 } 539 //@ts-ignore 540 if (args.tid.length > 0) { 541 //@ts-ignore 542 str += `or tid in (${args.tid.join(',')})`; 543 } 544 //@ts-ignore 545 if (args.pid.length > 0) { 546 //@ts-ignore 547 str += `or process_id in (${args.pid.join(',')})`; 548 } 549 str = str.slice(3); 550 let eventStr: string = ``; 551 //@ts-ignore 552 if (args.eventId) { 553 //@ts-ignore 554 eventStr = `AND eventTypeId = ${args.eventId}`; 555 } 556 this.queryData(this.currentEventId, 'perf-async', ` 557 select 558 ts - R.start_ts as time, 559 traceid, 560 thread_id as tid, 561 process_id as pid, 562 caller_callchainid as callerCallchainid, 563 callee_callchainid as calleeCallchainid, 564 perf_sample_id as perfSampleId, 565 event_count as eventCount, 566 event_type_id as eventTypeId 567 from 568 perf_napi_async A, trace_range R 569 WHERE 570 (` + str + `)` + eventStr + ` 571 AND 572 time between ${ 573 //@ts-ignore 574 args.leftNs} and ${args.rightNs} 575 `, {}); 576 } 577 578 /** 579 * 580 * @param selectionParam 581 * @param sql 从饼图进程或者线程层点击进入Perf Profile时传入 582 */ 583 private getCurrentDataFromDb(selectionParam: SelectionParam, flag: string, sql?: string): void { 584 let filterSql = this.setFilterSql(selectionParam, sql); 585 this.queryData( 586 this.currentEventId, 587 `${flag}`, 588 `select p.callchain_id as sampleId, 589 p.thread_state as threadState, 590 p.thread_id as tid, 591 p.count as count, 592 p.process_id as pid, 593 p.event_count as eventCount, 594 p.ts as ts, 595 p.event_type_id as eventTypeId 596 from (select callchain_id, s.thread_id, s.event_type_id, thread_state, process_id, 597 count(callchain_id) as count,SUM(event_count) as event_count, 598 group_concat(s.timestamp_trace - t.start_ts,',') as ts 599 from perf_sample s, trace_range t 600 left join perf_thread thread on s.thread_id = thread.thread_id 601 where timestamp_trace between ${selectionParam.leftNs} + t.start_ts 602 and ${selectionParam.rightNs} + t.start_ts 603 and callchain_id != -1 604 and s.thread_id != 0 ${filterSql} 605 group by callchain_id, s.thread_id, thread_state, process_id) p`, 606 { 607 $startTime: selectionParam.leftNs, 608 $endTime: selectionParam.rightNs, 609 $sql: filterSql, 610 } 611 ); 612 } 613 614 private setFilterSql(selectionParam: SelectionParam, sql?: string): string { 615 let filterSql = ''; 616 if (sql) { 617 const cpus = selectionParam.perfAll ? [] : selectionParam.perfCpus; 618 const cpuFilter = cpus.length > 0 ? ` and s.cpu_id in (${cpus.join(',')}) ` : ''; 619 let arg = `${sql}${cpuFilter}`.substring(3); 620 filterSql = `and ${arg}`; 621 } else { 622 const cpus = selectionParam.perfAll ? [] : selectionParam.perfCpus; 623 const processes = selectionParam.perfAll ? [] : selectionParam.perfProcess; 624 const threads = selectionParam.perfAll ? [] : selectionParam.perfThread; 625 if (cpus.length !== 0 || processes.length !== 0 || threads.length !== 0) { 626 const cpuFilter = cpus.length > 0 ? `or s.cpu_id in (${cpus.join(',')}) ` : ''; 627 const processFilter = processes.length > 0 ? `or thread.process_id in (${processes.join(',')}) ` : ''; 628 const threadFilter = threads.length > 0 ? `or s.thread_id in (${threads.join(',')})` : ''; 629 let arg = `${cpuFilter}${processFilter}${threadFilter}`.substring(3); 630 filterSql = ` and (${arg})`; 631 } 632 } 633 let eventTypeId = selectionParam.perfEventTypeId; 634 const eventTypeFilter = eventTypeId !== undefined ? ` and s.event_type_id = ${eventTypeId}` : ''; 635 filterSql += eventTypeFilter; 636 return filterSql; 637 } 638 639 clearAll(): void { 640 this.filesData = {}; 641 this.samplesData = []; 642 this.threadData = {}; 643 this.callChainData = {}; 644 this.splitMapData = {}; 645 this.currentTreeMapData = {}; 646 this.currentTreeList = []; 647 this.searchValue = ''; 648 this.dataSource = []; 649 this.allProcess = []; 650 this.dataCache.clearPerf(); 651 } 652 653 initPerfCallChainTopDown(callChains: PerfCallChain[]): void { 654 this.callChainData = {}; 655 callChains.forEach((callChain: PerfCallChain, index: number): void => { 656 if (callChain.sourceFileId) { 657 const sourceFile = DataCache.getInstance().dataDict.get(callChain.sourceFileId) || ''; 658 const symbolName = DataCache.getInstance().dataDict.get(callChain.name as number) || ''; 659 let lines = this.lineMap.get(`${sourceFile}_${symbolName}`); 660 if (lines === undefined) { 661 lines = new Set<number>(); 662 this.lineMap.set(`${sourceFile}_${symbolName}`, lines); 663 } 664 lines.add(callChain.lineNumber); 665 } 666 this.setPerfCallChainFrameName(callChain); 667 this.addPerfGroupData(callChain); 668 let callChainDatum = this.callChainData[callChain.sampleId]; 669 if (callChainDatum.length > 1) { 670 PerfCallChain.setNextNode(callChainDatum[callChainDatum.length - 2], callChainDatum[callChainDatum.length - 1]); 671 } 672 }); 673 } 674 675 setPerfCallChainFrameName(callChain: PerfCallChain): void { 676 //设置调用栈的名称 677 callChain.canCharge = true; 678 if (this.filesData[callChain.fileId] && this.filesData[callChain.fileId].length > 0) { 679 callChain.fileName = this.filesData[callChain.fileId][0].fileName; 680 callChain.path = this.filesData[callChain.fileId][0].path; 681 } else { 682 callChain.fileName = 'unknown'; 683 } 684 } 685 686 addPerfGroupData(callChain: PerfCallChain): void { 687 const currentCallChain = this.callChainData[callChain.sampleId] || []; 688 this.callChainData[callChain.sampleId] = currentCallChain; 689 if (currentCallChain.length > maxDepth) { 690 currentCallChain.splice(0, 1); 691 } 692 currentCallChain.push(callChain); 693 } 694 695 addOtherCallchainsData(countSample: PerfCountSample, list: PerfCallChain[]): void { 696 let threadCallChain = new PerfCallChain(); //新增的线程数据 697 threadCallChain.tid = countSample.tid; 698 threadCallChain.canCharge = false; 699 threadCallChain.isThread = true; 700 threadCallChain.name = `${this.threadData[countSample.tid].threadName || 'Thread'}(${countSample.tid})`; 701 let threadStateCallChain = new PerfCallChain(); //新增的线程状态数据 702 threadStateCallChain.tid = countSample.tid; 703 threadStateCallChain.isThreadState = true; 704 threadStateCallChain.name = countSample.threadState || 'Unknown State'; 705 threadStateCallChain.fileName = threadStateCallChain.name === '-' ? 'Unknown Thread State' : ''; 706 threadStateCallChain.canCharge = false; 707 if (!this.isHideThreadState) { 708 list.unshift(threadStateCallChain); 709 } 710 if (!this.isHideThread) { 711 list.unshift(threadCallChain); 712 } 713 714 if (this.isOnlyKernel) { 715 const flag = '[kernel.kallsyms]'; 716 const newList = list.filter(i => i.fileName === flag || i.path === flag); 717 list.splice(0); 718 list.push(...newList); 719 } 720 } 721 722 private freshPerfCallchains(perfCountSamples: PerfCountSample[], isTopDown: boolean): void { 723 this.currentTreeMapData = {}; 724 this.currentTreeList = []; 725 let totalSamplesCount = 0; 726 let totalEventCount = 0; 727 perfCountSamples.forEach((perfSample): void => { 728 totalSamplesCount += perfSample.count; 729 totalEventCount += perfSample.eventCount; 730 if (this.callChainData[perfSample.sampleId] && this.callChainData[perfSample.sampleId].length > 0) { 731 let perfCallChains = [...this.callChainData[perfSample.sampleId]]; 732 this.addOtherCallchainsData(perfSample, perfCallChains); 733 let topIndex = isTopDown ? 0 : perfCallChains.length - 1; 734 if (perfCallChains.length > 0) { 735 let symbolName = ''; 736 if (typeof perfCallChains[topIndex].name === 'number') { 737 //@ts-ignore 738 symbolName = this.dataCache.dataDict.get(perfCallChains[topIndex].name) || ''; 739 } else { 740 //@ts-ignore 741 symbolName = perfCallChains[topIndex].name; 742 } 743 // 只展示内核栈合并进程栈 744 const usePidAsKey = this.isOnlyKernel ? '' : perfSample.pid; 745 let perfRootNode = this.currentTreeMapData[symbolName + usePidAsKey]; 746 if (perfRootNode === undefined) { 747 perfRootNode = new PerfCallChainMerageData(); 748 this.currentTreeMapData[symbolName + usePidAsKey] = perfRootNode; 749 this.currentTreeList.push(perfRootNode); 750 } 751 PerfCallChainMerageData.merageCallChainSample(perfRootNode, perfCallChains[topIndex], perfSample, false, this.lineMap); 752 this.mergeChildrenByIndex(perfRootNode, perfCallChains, topIndex, perfSample, isTopDown); 753 } 754 } 755 }); 756 let rootMerageMap = this.mergeNodeData(totalEventCount, totalSamplesCount); 757 this.handleCurrentTreeList(totalEventCount, totalSamplesCount); 758 this.allProcess = Object.values(rootMerageMap); 759 // 浅拷贝 760 this.forkAllProcess = this.allProcess.slice(); 761 } 762 private mergeNodeData(totalEventCount: number, totalSamplesCount: number): MergeMap { 763 // 只展示内核栈不添加进程这一级的结构 764 if (this.isOnlyKernel) { 765 return this.currentTreeMapData; 766 } 767 // 添加进程级结构 768 let rootMerageMap: MergeMap = {}; 769 // @ts-ignore 770 Object.values(this.currentTreeMapData).forEach((merageData: PerfCallChainMerageData): void => { 771 if (rootMerageMap[merageData.pid] === undefined) { 772 let perfProcessMerageData = new PerfCallChainMerageData(); //新增进程的节点数据 773 perfProcessMerageData.canCharge = false; 774 perfProcessMerageData.symbolName = 775 (this.threadData[merageData.tid].processName || 'Process') + `(${merageData.pid})`; 776 perfProcessMerageData.isProcess = true; 777 perfProcessMerageData.symbol = perfProcessMerageData.symbolName; 778 perfProcessMerageData.tid = merageData.tid; 779 perfProcessMerageData.children.push(merageData); 780 perfProcessMerageData.initChildren.push(merageData); 781 perfProcessMerageData.dur = merageData.dur; 782 perfProcessMerageData.count = merageData.dur; 783 perfProcessMerageData.eventCount = merageData.eventCount; 784 perfProcessMerageData.total = totalSamplesCount; 785 perfProcessMerageData.totalEvent = totalEventCount; 786 perfProcessMerageData.tsArray = [...merageData.tsArray]; 787 rootMerageMap[merageData.pid] = perfProcessMerageData; 788 } else { 789 rootMerageMap[merageData.pid].children.push(merageData); 790 rootMerageMap[merageData.pid].initChildren.push(merageData); 791 rootMerageMap[merageData.pid].dur += merageData.dur; 792 rootMerageMap[merageData.pid].count += merageData.dur; 793 rootMerageMap[merageData.pid].eventCount += merageData.eventCount; 794 rootMerageMap[merageData.pid].total = totalSamplesCount; 795 rootMerageMap[merageData.pid].totalEvent = totalEventCount; 796 for (const ts of merageData.tsArray) { 797 rootMerageMap[merageData.pid].tsArray.push(ts); 798 } 799 } 800 merageData.parentNode = rootMerageMap[merageData.pid]; //子节点添加父节点的引用 801 }); 802 return rootMerageMap; 803 } 804 private handleCurrentTreeList(totalEventCount: number, totalSamplesCount: number): void { 805 let id = 0; 806 this.currentTreeList.forEach((perfTreeNode: PerfCallChainMerageData): void => { 807 perfTreeNode.total = totalSamplesCount; 808 perfTreeNode.totalEvent = totalEventCount; 809 if (perfTreeNode.id === '') { 810 perfTreeNode.id = id + ''; 811 id++; 812 } 813 if (perfTreeNode.parentNode) { 814 if (perfTreeNode.parentNode.id === '') { 815 perfTreeNode.parentNode.id = id + ''; 816 id++; 817 } 818 perfTreeNode.parentId = perfTreeNode.parentNode.id; 819 } 820 }); 821 } 822 823 mergeChildrenByIndex( 824 currentNode: PerfCallChainMerageData, 825 callChainDataList: PerfCallChain[], 826 index: number, 827 sample: PerfCountSample, 828 isTopDown: boolean 829 ): void { 830 if ((isTopDown && index >= callChainDataList.length - 1) || (!isTopDown && index <= 0)) { 831 return; 832 } 833 isTopDown ? index++ : index--; 834 let isEnd = isTopDown ? callChainDataList.length === index + 1 : index === 0; 835 let node: PerfCallChainMerageData; 836 if ( 837 currentNode.initChildren.filter((child: PerfCallChainMerageData): boolean => { 838 let name: number | string | undefined = callChainDataList[index].name; 839 if (typeof name === 'number') { 840 name = this.dataCache.dataDict.get(name); 841 } 842 if (child.symbolName === name) { 843 node = child; 844 PerfCallChainMerageData.merageCallChainSample(child, callChainDataList[index], sample, isEnd, this.lineMap); 845 return true; 846 } 847 return false; 848 }).length === 0 849 ) { 850 node = new PerfCallChainMerageData(); 851 PerfCallChainMerageData.merageCallChainSample(node, callChainDataList[index], sample, isEnd, this.lineMap); 852 currentNode.children.push(node); 853 currentNode.initChildren.push(node); 854 this.currentTreeList.push(node); 855 node.parentNode = currentNode; 856 } 857 if (node! && !isEnd) { 858 this.mergeChildrenByIndex(node, callChainDataList, index, sample, isTopDown); 859 } 860 } 861 862 //所有的操作都是针对整个树结构的 不区分特定的数据 863 splitPerfTree(samples: PerfCallChainMerageData[], name: string, isCharge: boolean, isSymbol: boolean): void { 864 samples.forEach((process: PerfCallChainMerageData): void => { 865 process.children = []; 866 if (isCharge) { 867 this.recursionPerfChargeInitTree(process, name, isSymbol); 868 } else { 869 this.recursionPerfPruneInitTree(process, name, isSymbol); 870 } 871 }); 872 this.resetAllNode(samples); 873 } 874 875 recursionPerfChargeInitTree(sample: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void { 876 if ((isSymbol && sample.symbolName === symbolName) || (!isSymbol && sample.libName === symbolName)) { 877 (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(sample); 878 sample.isStore++; 879 } 880 if (sample.initChildren.length > 0) { 881 sample.initChildren.forEach((child: PerfCallChainMerageData): void => { 882 this.recursionPerfChargeInitTree(child, symbolName, isSymbol); 883 }); 884 } 885 } 886 887 recursionPerfPruneInitTree(node: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void { 888 if ((isSymbol && node.symbolName === symbolName) || (!isSymbol && node.libName === symbolName)) { 889 (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(node); 890 node.isStore++; 891 this.pruneChildren(node, symbolName); 892 } else if (node.initChildren.length > 0) { 893 node.initChildren.forEach((child): void => { 894 this.recursionPerfPruneInitTree(child, symbolName, isSymbol); 895 }); 896 } 897 } 898 899 //symbol lib prune 900 recursionPruneTree(sample: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void { 901 if ((isSymbol && sample.symbolName === symbolName) || (!isSymbol && sample.libName === symbolName)) { 902 sample.parent && sample.parent.children.splice(sample.parent.children.indexOf(sample), 1); 903 } else { 904 sample.children.forEach((child: PerfCallChainMerageData): void => { 905 this.recursionPruneTree(child, symbolName, isSymbol); 906 }); 907 } 908 } 909 910 recursionChargeByRule( 911 sample: PerfCallChainMerageData, 912 ruleName: string, 913 rule: (node: PerfCallChainMerageData) => boolean 914 ): void { 915 if (sample.initChildren.length > 0) { 916 sample.initChildren.forEach((child): void => { 917 if (rule(child) && !child.isThread && !child.isState) { 918 (this.splitMapData[ruleName] = this.splitMapData[ruleName] || []).push(child); 919 child.isStore++; 920 } 921 this.recursionChargeByRule(child, ruleName, rule); 922 }); 923 } 924 } 925 926 pruneChildren(sample: PerfCallChainMerageData, symbolName: string): void { 927 if (sample.initChildren.length > 0) { 928 sample.initChildren.forEach((child: PerfCallChainMerageData): void => { 929 child.isStore++; 930 (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(child); 931 this.pruneChildren(child, symbolName); 932 }); 933 } 934 } 935 936 hideSystemLibrary(): void { 937 this.allProcess.forEach((item: PerfCallChainMerageData): void => { 938 item.children = []; 939 this.recursionChargeByRule(item, systemRuleName, (node: PerfCallChainMerageData): boolean => { 940 return node.path.startsWith(systemRuleName); 941 }); 942 }); 943 } 944 945 hideNumMaxAndMin(startNum: number, endNum: string): void { 946 let max = endNum === '∞' ? Number.POSITIVE_INFINITY : parseInt(endNum); 947 this.allProcess.forEach((item: PerfCallChainMerageData): void => { 948 item.children = []; 949 // only kernel模式下第0层结构为调用栈,也需要变化 950 if (this.isOnlyKernel && (item.dur < startNum || item.dur > max)) { 951 (this.splitMapData[numRuleName] = this.splitMapData[numRuleName] || []).push(item); 952 item.isStore++; 953 } 954 this.recursionChargeByRule(item, numRuleName, (node: PerfCallChainMerageData): boolean => { 955 return node.dur < startNum || node.dur > max; 956 }); 957 }); 958 } 959 960 clearSplitMapData(symbolName: string): void { 961 if (symbolName in this.splitMapData) { 962 Reflect.deleteProperty(this.splitMapData, symbolName); 963 } 964 } 965 966 resetAllSymbol(symbols: string[]): void { 967 // 浅拷贝取出备份数据 968 this.allProcess = this.forkAllProcess.slice(); 969 symbols.forEach((symbol: string): void => { 970 let list = this.splitMapData[symbol]; 971 if (list !== undefined) { 972 list.forEach((item: PerfCallChainMerageData): void => { 973 item.isStore--; 974 }); 975 } 976 }); 977 } 978 979 980 clearSearchNode(): void { 981 this.currentTreeList.forEach((sample: PerfCallChainMerageData): void => { 982 sample.searchShow = true; 983 sample.isSearch = false; 984 }); 985 } 986 987 resetAllNode(sample: PerfCallChainMerageData[]): void { 988 this.allProcess = this.forkAllProcess.slice(); 989 if (this.isOnlyKernel) { 990 this.markSearchNode(this.allProcess, this.searchValue, false); 991 this.resetNewAllNode(sample); 992 } else { 993 this.clearSearchNode(); 994 sample.forEach((process: PerfCallChainMerageData): void => { 995 process.searchShow = true; 996 process.isSearch = false; 997 }); 998 this.resetNewAllNode(sample); 999 this.searchValue = this.searchValue.toLocaleLowerCase(); 1000 let researchValue = this.searchValue.substring(1, this.searchValue.length); 1001 /* 1002 *开头,正则表达式 1003 非*开头,普通筛选 1004 '!'开头,普通反选 1005 */ 1006 if (this.searchValue[0] === '!') { 1007 this.reMarkSearchNode(sample, researchValue, true); 1008 } else { 1009 if (this.searchValue[0] === '*') { 1010 // 正则反选 1011 if (this.searchValue[1] === '^') { 1012 this.markUnRegexSearchNode(this.allProcess, researchValue, true); 1013 } else { 1014 // 正则正选 1015 this.markRegexSearchNode(this.allProcess, researchValue, false); 1016 } 1017 } else { 1018 // 普通正选 1019 this.markSearchNode(sample, this.searchValue, false); 1020 } 1021 } 1022 this.resetNewAllNode(sample); 1023 } 1024 } 1025 1026 /** 1027 * 重置所有节点的子节点列表,并根据条件重新构建子节点列表 1028 * 此函数旨在清理和重新组织 PerfCallChainMerageData 类型的样本数组中的节点关系 1029 * 它通过移除某些节点并重新分配子节点来更新树结构 1030 * 1031 * @param sampleArray - lastShowNode 1032 */ 1033 resetNewAllNode(sampleArray: PerfCallChainMerageData[]): void { 1034 sampleArray.forEach((process: PerfCallChainMerageData): void => { 1035 process.children = []; 1036 }); 1037 let values = this.currentTreeList.map((item: PerfCallChainMerageData): PerfCallChainMerageData => { 1038 item.children = []; 1039 return item; 1040 }); 1041 // 记录待删除的节点索引 1042 const removeList: number[] = []; 1043 // 用于记录所有有效的子节点 1044 const effectChildList: PerfCallChainMerageData[] = []; 1045 for (const sample of values) { 1046 if (sample.parentNode !== undefined && sample.isStore === 0 && sample.searchShow) { 1047 let parentNode = sample.parentNode; 1048 while (parentNode !== undefined && !(parentNode.isStore === 0 && parentNode.searchShow)) { 1049 parentNode = parentNode.parentNode!; 1050 } 1051 if (parentNode) { 1052 sample.parent = parentNode; 1053 parentNode.children.push(sample); 1054 } 1055 } else { 1056 // 如果节点没有父节点且是存储节点或者搜索不显示,则将其标记为待删除,并检查其子节点中是否有需要保留的 1057 if (!sample.parentNode && (sample.isStore > 0 || !sample.searchShow)) { 1058 removeList.push(this.allProcess.indexOf(sample)); 1059 const effectChildren = this.findEffectChildren(sample.initChildren); 1060 if (effectChildren.length > 0) { 1061 effectChildList.push(...effectChildren); 1062 } 1063 } 1064 } 1065 } 1066 // 删除标记为待删除的节点 1067 removeList.sort((a, b) => b - a).forEach(index => { 1068 if (index >= 0 && index < values.length) { 1069 this.allProcess.splice(index, 1); 1070 } 1071 }); 1072 // 将所有有效的子节点添加到返回的列表中 1073 this.allProcess.push(...effectChildList); 1074 } 1075 1076 /** 1077 * 递归查找调用链中非存储且标记为显示的子节点 1078 * 1079 * 本函数旨在筛选出调用链数据中,所有非存储(isStore为0)且被标记为需要显示(searchShow为true)的节点 1080 * 它通过递归遍历每个节点的子节点(initChildren),确保所有符合条件的节点都被找出 1081 * 1082 * @param children 调用链的子节点数组,这些子节点是待筛选的数据 1083 * @returns 返回一个新数组,包含所有非存储且标记为显示的子节点 1084 */ 1085 private findEffectChildren(children: PerfCallChainMerageData[]): PerfCallChainMerageData[] { 1086 let result: PerfCallChainMerageData[] = []; 1087 for (let child of children) { 1088 // 如果搜索框有值,检查当前子树是否有任何一个节点的 isSearch 为 true 1089 if (this.searchValue === '' || this.hasSearchNode(child)) { 1090 // 如果当前节点非存储且需要显示,则直接添加到结果数组中 1091 if (child.isStore === 0 && child.searchShow) { 1092 result.push(child); 1093 } else { 1094 // 如果当前节点不符合条件,递归查找其子节点中符合条件的节点 1095 result.push(...this.findEffectChildren(child.initChildren)); 1096 } 1097 } 1098 } 1099 // 返回结果数组 1100 return result; 1101 } 1102 1103 /** 1104 * 检查性能调用链合并数据中的节点是否包含搜索节点 1105 * 1106 * 该函数采用递归方式遍历节点及其子节点,以确定是否至少存在一个搜索节点 1107 * 主要用于在性能数据结构中快速定位是否有符合搜索条件的节点 1108 * 1109 * @param node 当前遍历的节点,类型为PerfCallChainMerageData 1110 * @returns 如果找到搜索节点则返回true,否则返回false 1111 */ 1112 private hasSearchNode(node: PerfCallChainMerageData): boolean { 1113 if (node.isSearch) { 1114 return true; 1115 } 1116 for (let child of node.initChildren) { 1117 if (this.hasSearchNode(child)) { 1118 return true; 1119 } 1120 } 1121 return false; 1122 } 1123 1124 kernelCombination(): void { 1125 function mergeChildren(item: PerfCallChainMerageData): void { 1126 if (item.children.length <= 0) { 1127 return; 1128 } 1129 item.children = item.children.reduce((total: PerfCallChainMerageData[], pfcall: PerfCallChainMerageData): PerfCallChainMerageData[] => { 1130 for (const prev of total) { 1131 if (pfcall.symbol === prev.symbol) { 1132 prev.children.push(...pfcall.children); 1133 prev.total += pfcall.total; 1134 prev.count += pfcall.count; 1135 prev.totalEvent += pfcall.totalEvent; 1136 prev.eventCount += pfcall.eventCount; 1137 return total; 1138 } 1139 } 1140 total.push(pfcall); 1141 return total; 1142 }, [] as PerfCallChainMerageData[]); 1143 for (const child of item.children) { 1144 mergeChildren(child); 1145 } 1146 } 1147 this.allProcess.forEach((item: PerfCallChainMerageData): void => { 1148 mergeChildren(item); 1149 }); 1150 } 1151 1152 // 普通正向筛选 1153 markSearchNode(sampleArray: PerfCallChainMerageData[], search: string, parentSearch: boolean): void { 1154 for (const sample of sampleArray) { 1155 if (search === '') { 1156 sample.searchShow = true; 1157 sample.isSearch = false; 1158 // 将反选标记全部清除 1159 sample.hiddenArray = []; 1160 sample.isReverseFilter = false; 1161 } else { 1162 let isInclude = sample.symbol.toLocaleLowerCase().includes(search); 1163 if ((sample.symbol && isInclude) || parentSearch) { 1164 sample.searchShow = true; 1165 sample.isSearch = sample.symbol !== undefined && isInclude; 1166 let parentNode = sample.parent; 1167 // 如果匹配,所有parent都显示 1168 while (parentNode !== undefined && !parentNode.searchShow) { 1169 parentNode.searchShow = true; 1170 parentNode = parentNode.parent; 1171 } 1172 } else { 1173 sample.searchShow = false; 1174 sample.isSearch = false; 1175 } 1176 } 1177 const children = this.isOnlyKernel ? sample.initChildren : sample.children; 1178 if (children.length > 0) { 1179 this.markSearchNode(children, search, sample.searchShow); 1180 } 1181 } 1182 } 1183 1184 reMarkSearchNode(sampleArray: PerfCallChainMerageData[], search: string, parentSearch: boolean): void { 1185 for (const sample of sampleArray) { 1186 if (search === '') { 1187 sample.searchShow = true; 1188 sample.isReverseFilter = false; 1189 } else { 1190 // 反选符合要求的 1191 let isInclude = !sample.symbol.toLocaleLowerCase().includes(search); 1192 // 从上往下遍历,父节点不展示的直接不展示,并且标记为置为false 1193 if (!parentSearch) { 1194 sample.searchShow = false 1195 } else { 1196 // 父节点展示并符合要求的直接展示 1197 if (sample.symbol && isInclude) { 1198 sample.isReverseFilter = true; 1199 sample.searchShow = true; 1200 } else { 1201 // 父节点展示子节点不展示的,反向向上遍历置false 1202 sample.isSearch = false; 1203 sample.searchShow = false; 1204 let parentNode = sample.parent 1205 parentNode?.hiddenArray.push(sample.symbolName); 1206 // 多个子节点,只有所有子节点都不展示的才可以不展示并且继续向上遍历 1207 while (parentNode && parentNode.children.length === parentNode.hiddenArray.length) { 1208 parentNode.searchShow = false; 1209 if (parentNode.parent?.hiddenArray.indexOf(parentNode.symbolName) === -1) { 1210 parentNode.parent?.hiddenArray.push(parentNode.symbolName) 1211 } 1212 parentNode = parentNode.parent; 1213 } 1214 } 1215 } 1216 } 1217 const children = this.isOnlyKernel ? sample.initChildren : sample.children; 1218 if (children.length > 0) { 1219 this.reMarkSearchNode(children, search, sample.searchShow); 1220 } 1221 } 1222 } 1223 1224 markUnRegexSearchNode(sampleArray: PerfCallChainMerageData[], reg: string, parentSearch: boolean): void { 1225 let regex = RegExp(reg); 1226 for (const sample of sampleArray) { 1227 // 反选符合要求的 1228 let isInclude = regex.test(sample.symbol.toLocaleLowerCase()); 1229 if (!parentSearch) { 1230 sample.searchShow = false 1231 } else { 1232 if (sample.symbol && isInclude) { 1233 sample.isReverseFilter = true; 1234 sample.searchShow = true; 1235 } else { 1236 sample.isSearch = false; 1237 sample.searchShow = false; 1238 let parentNode = sample.parent 1239 parentNode?.hiddenArray.push(sample.symbolName); 1240 while (parentNode && parentNode.children.length === parentNode.hiddenArray.length) { 1241 parentNode.searchShow = false; 1242 if (parentNode.parent?.hiddenArray.indexOf(parentNode.symbolName) === -1) { 1243 parentNode.parent?.hiddenArray.push(parentNode.symbolName) 1244 } 1245 parentNode = parentNode.parent; 1246 } 1247 } 1248 } 1249 const children = this.isOnlyKernel ? sample.initChildren : sample.children; 1250 if (children.length > 0) { 1251 this.markUnRegexSearchNode(children, reg, sample.searchShow); 1252 } 1253 } 1254 } 1255 1256 markRegexSearchNode(sampleArray: PerfCallChainMerageData[], reg: string, parentSearch: boolean): void { 1257 let regex = RegExp(reg); 1258 for (const sample of sampleArray) { 1259 let isInclude = regex.test(sample.symbol.toLocaleLowerCase()); 1260 if ((sample.symbol && isInclude) || parentSearch) { 1261 sample.searchShow = true; 1262 sample.isSearch = sample.symbol !== undefined && isInclude; 1263 let parentNode = sample.parent; 1264 // 如果匹配,所有parent都显示 1265 while (parentNode !== undefined && !parentNode.searchShow) { 1266 parentNode.searchShow = true; 1267 parentNode = parentNode.parent; 1268 } 1269 } else { 1270 sample.searchShow = false; 1271 sample.isSearch = false; 1272 } 1273 1274 const children = this.isOnlyKernel ? sample.initChildren : sample.children; 1275 if (children.length > 0) { 1276 this.markRegexSearchNode(children, reg, sample.searchShow); 1277 } 1278 } 1279 } 1280 1281 splitAllProcess(processArray: { select: string; name: string; type: string; checked: boolean }[]): void { 1282 processArray.forEach((item: { select: string; name: string; type: string; checked: boolean }): void => { 1283 this.allProcess.forEach((process): void => { 1284 if (item.select === '0') { 1285 this.recursionPerfChargeInitTree(process, item.name, item.type === 'symbol'); 1286 } else { 1287 this.recursionPerfPruneInitTree(process, item.name, item.type === 'symbol'); 1288 } 1289 }); 1290 if (!item.checked) { 1291 this.resetAllSymbol([item.name]); 1292 } 1293 }); 1294 } 1295 resolvingAction(params: unknown[]): unknown[] { 1296 if (params.length > 0) { 1297 for (let item of params) { 1298 //@ts-ignore 1299 if (item.funcName && item.funcArgs) { 1300 //@ts-ignore 1301 let result = this.handleDataByFuncName(item.funcName, item.funcArgs); 1302 if (result) { 1303 //@ts-ignore 1304 return result; 1305 } 1306 } 1307 } 1308 if (this.isOnlyKernel) { 1309 this.dataSource = this.allProcess; 1310 } else { 1311 this.dataSource = this.allProcess.filter((process: PerfCallChainMerageData): boolean => { 1312 return process.children && process.children.length > 0; 1313 }); 1314 } 1315 } 1316 return this.dataSource; 1317 } 1318 private queryDataFromDb(funcArgs: unknown[], flag: string): void { 1319 if (funcArgs[1]) { 1320 let sql = ''; 1321 //@ts-ignore 1322 if (funcArgs[1].processId !== undefined) { 1323 //@ts-ignore 1324 sql += `and thread.process_id = ${funcArgs[1].processId}`; 1325 } 1326 //@ts-ignore 1327 if (funcArgs[1].threadId !== undefined) { 1328 //@ts-ignore 1329 sql += ` and s.thread_id = ${funcArgs[1].threadId}`; 1330 } 1331 //@ts-ignore 1332 this.getCurrentDataFromDb(funcArgs[0], flag, sql); 1333 } else { 1334 //@ts-ignore 1335 this.getCurrentDataFromDb(funcArgs[0], flag, funcArgs[1]); 1336 } 1337 } 1338 1339 private queryVaddrToFile(funcArgs: unknown[]): void { 1340 if (funcArgs[1]) { 1341 let sql = ''; 1342 //@ts-ignore 1343 if (funcArgs[1].processId !== undefined) { 1344 //@ts-ignore 1345 sql += `and thread.process_id = ${funcArgs[1].processId}`; 1346 } 1347 //@ts-ignore 1348 if (funcArgs[1].threadId !== undefined) { 1349 //@ts-ignore 1350 sql += ` and s.thread_id = ${funcArgs[1].threadId}`; 1351 } 1352 //@ts-ignore 1353 this.getVaddrToFile(funcArgs[0], sql); 1354 } else { 1355 //@ts-ignore 1356 this.getVaddrToFile(funcArgs[0]); 1357 } 1358 } 1359 1360 private getVaddrToFile(selectionParam: SelectionParam, sql?: string): void { 1361 let filterSql = this.setFilterSql(selectionParam, sql); 1362 this.queryData( 1363 this.currentEventId, 1364 'perf-vaddr-back', 1365 `select s.callchain_id, 1366 s.thread_id, 1367 thread.process_id, 1368 count(callchain_id) as count 1369 from perf_sample s, trace_range t 1370 left join perf_thread thread on s.thread_id = thread.thread_id 1371 where timestamp_trace between ${selectionParam.leftNs} + t.start_ts 1372 and ${selectionParam.rightNs} + t.start_ts 1373 and s.callchain_id != -1 1374 and s.thread_id != 0 ${filterSql} 1375 group by s.callchain_id,s.thread_id`, 1376 { 1377 $startTime: selectionParam.leftNs, 1378 $endTime: selectionParam.rightNs, 1379 $sql: filterSql, 1380 } 1381 ); 1382 } 1383 1384 private handleDataByFuncName(funcName: string, funcArgs: unknown[]): unknown { 1385 let result; 1386 switch (funcName) { 1387 case 'getCallChainsBySampleIds': 1388 this.freshPerfCallchains(this.samplesData, funcArgs[0] as boolean); 1389 break; 1390 case 'getCurrentDataFromDbProfile': 1391 this.queryDataFromDb(funcArgs, 'perf-profile'); 1392 break; 1393 case 'getCurrentDataFromDbAnalysis': 1394 this.queryDataFromDb(funcArgs, 'perf-analysis'); 1395 break; 1396 case 'getCurrentDataFromDbBottomUp': 1397 this.queryDataFromDb(funcArgs, 'perf-bottomUp'); 1398 case 'getVaddrToFile': 1399 this.queryVaddrToFile(funcArgs); 1400 break; 1401 case 'hideSystemLibrary': 1402 this.hideSystemLibrary(); 1403 break; 1404 case 'hideThread': 1405 this.isHideThread = funcArgs[0] as boolean; 1406 break; 1407 case 'hideThreadState': 1408 this.isHideThreadState = funcArgs[0] as boolean; 1409 break; 1410 case 'onlyKernel': 1411 this.isOnlyKernel = funcArgs[0] as boolean; 1412 break; 1413 case 'hideNumMaxAndMin': 1414 this.hideNumMaxAndMin(funcArgs[0] as number, funcArgs[1] as string); 1415 break; 1416 case 'splitAllProcess': 1417 //@ts-ignore 1418 this.splitAllProcess(funcArgs[0]); 1419 break; 1420 case 'resetAllNode': 1421 this.resetAllNode(this.allProcess); 1422 break; 1423 case 'resotreAllNode': 1424 this.resetAllSymbol(funcArgs[0] as string[]); 1425 break; 1426 case 'clearSplitMapData': 1427 this.clearSplitMapData(funcArgs[0] as string); 1428 break; 1429 case 'splitTree': 1430 this.splitPerfTree(this.allProcess, funcArgs[0] as string, funcArgs[1] as boolean, funcArgs[2] as boolean); 1431 break; 1432 case 'setSearchValue': 1433 this.searchValue = funcArgs[0] as string; 1434 break; 1435 1436 case 'combineAnalysisCallChain': 1437 result = this.combineCallChainForAnalysis(); 1438 break; 1439 case 'getBottomUp': 1440 result = this.getBottomUp(); 1441 break; 1442 case 'kernelCombination': 1443 this.kernelCombination(); 1444 break; 1445 } 1446 return result; 1447 } 1448 1449 combineCallChainForAnalysis(obj?: unknown): PerfAnalysisSample[] { 1450 let sampleCallChainList: Array<PerfAnalysisSample> = []; 1451 for (let sample of this.samplesData) { 1452 if (!this.callChainData[sample.sampleId]) { 1453 continue; 1454 } 1455 let callChains = [...this.callChainData[sample.sampleId]]; 1456 const lastCallChain = callChains[callChains.length - 1]; 1457 const threadName = this.threadData[sample.tid].threadName || 'Thread'; 1458 const processName = this.threadData[sample.pid] ? this.threadData[sample.pid].threadName : 'Process'; 1459 const funcName = this.dataCache.dataDict.get(lastCallChain.name as number); 1460 if ( 1461 //@ts-ignore 1462 (obj && obj.libId === lastCallChain.fileId && obj.libName === lastCallChain.fileName) || 1463 //@ts-ignore 1464 (obj && obj.symbolId === lastCallChain.symbolId && obj.symbolName === funcName) || 1465 !obj 1466 ) { 1467 let analysisSample = new PerfAnalysisSample( 1468 threadName, 1469 lastCallChain.depth, 1470 lastCallChain.vaddrInFile, 1471 lastCallChain.offsetToVaddr, 1472 processName, 1473 lastCallChain.fileId, 1474 lastCallChain.fileName, 1475 lastCallChain.symbolId, 1476 this.dataCache.dataDict.get(lastCallChain.name as number) || '' 1477 ); 1478 analysisSample.tid = sample.tid; 1479 analysisSample.pid = sample.pid; 1480 analysisSample.count = sample.count; 1481 analysisSample.threadState = sample.threadState; 1482 analysisSample.eventCount = sample.eventCount; 1483 analysisSample.sampleId = sample.sampleId; 1484 sampleCallChainList.push(analysisSample); 1485 } 1486 } 1487 return sampleCallChainList; 1488 } 1489 1490 getBottomUp(): PerfBottomUpStruct[] { 1491 const topUp = new PerfBottomUpStruct('topUp'); 1492 let perfTime = 1; 1493 for (let sample of this.samplesData) { 1494 let currentNode = topUp; 1495 let callChains = this.callChainData[sample.sampleId]; 1496 for (let i = 0; i < callChains.length; i++) { 1497 if (i === 0) { 1498 currentNode = topUp; 1499 } 1500 let item = callChains[i]; 1501 const existingNode = currentNode.children.find( 1502 (child) => child.symbolName === `${item.name}(${item.fileName})` 1503 ); 1504 if (existingNode) { 1505 existingNode.tsArray.push(...sample.ts.split(',').map(Number)); 1506 currentNode = existingNode; 1507 existingNode.totalTime += perfTime * sample.count; 1508 existingNode.eventCount += sample.eventCount; 1509 existingNode.calculateSelfTime(); 1510 existingNode.notifyParentUpdateSelfTime(); 1511 } else { 1512 const symbolName = this.dataCache.dataDict.get(item.name as number) || ''; 1513 let newNode = new PerfBottomUpStruct(`${symbolName}(${item.fileName})`); 1514 newNode.totalTime = perfTime * sample.count; 1515 newNode.eventCount = sample.eventCount; 1516 newNode.tsArray = sample.ts.split(',').map(Number); 1517 currentNode.addChildren(newNode); 1518 newNode.calculateSelfTime(); 1519 newNode.notifyParentUpdateSelfTime(); 1520 currentNode = newNode; 1521 } 1522 } 1523 } 1524 topUp.children.forEach((child: PerfBottomUpStruct): void => { 1525 child.parentNode = undefined; 1526 }); 1527 1528 let date = this.topUpDataToBottomUpData(topUp.children); 1529 if (this.isPerfBottomUp) { 1530 this.isPerfBottomUp = false; 1531 } 1532 return date; 1533 } 1534 1535 private topUpDataToBottomUpData(perfPositiveArray: Array<PerfBottomUpStruct>): Array<PerfBottomUpStruct> { 1536 let reverseTreeArray: Array<PerfBottomUpStruct> = []; 1537 const recursionTree = (perfBottomUpStruct: PerfBottomUpStruct): void => { 1538 if (perfBottomUpStruct.selfTime > 0) { 1539 const clonePerfBottomUpStruct = new PerfBottomUpStruct(perfBottomUpStruct.symbolName); 1540 clonePerfBottomUpStruct.selfTime = perfBottomUpStruct.selfTime; 1541 clonePerfBottomUpStruct.totalTime = perfBottomUpStruct.totalTime; 1542 clonePerfBottomUpStruct.eventCount = perfBottomUpStruct.eventCount; 1543 clonePerfBottomUpStruct.tsArray = [...perfBottomUpStruct.tsArray]; 1544 reverseTreeArray.push(clonePerfBottomUpStruct); 1545 this.copyParentNode(clonePerfBottomUpStruct, perfBottomUpStruct); 1546 } 1547 if (perfBottomUpStruct.children.length > 0) { 1548 for (const children of perfBottomUpStruct.children) { 1549 children.parentNode = perfBottomUpStruct; 1550 recursionTree(children); 1551 } 1552 } 1553 }; 1554 for (const perfBottomUpStruct of perfPositiveArray) { 1555 recursionTree(perfBottomUpStruct); 1556 } 1557 return this.mergeTreeBifurcation(reverseTreeArray, null); 1558 } 1559 1560 private mergeTreeBifurcation( 1561 reverseTreeArray: Array<PerfBottomUpStruct> | null, 1562 parent: PerfBottomUpStruct | null 1563 ): Array<PerfBottomUpStruct> { 1564 const sameSymbolMap = new Map<string, PerfBottomUpStruct>(); 1565 const currentLevelData: Array<PerfBottomUpStruct> = []; 1566 const dataArray = reverseTreeArray || parent?.frameChildren; 1567 if (!dataArray) { 1568 return []; 1569 } 1570 for (const perfBottomUpStruct of dataArray) { 1571 let symbolKey = perfBottomUpStruct.symbolName; 1572 let bottomUpStruct: PerfBottomUpStruct; 1573 if (sameSymbolMap.has(symbolKey)) { 1574 bottomUpStruct = sameSymbolMap.get(symbolKey)!; 1575 bottomUpStruct.totalTime += perfBottomUpStruct.totalTime; 1576 bottomUpStruct.selfTime += perfBottomUpStruct.selfTime; 1577 for (const ts of perfBottomUpStruct.tsArray) { 1578 bottomUpStruct.tsArray.push(ts); 1579 } 1580 } else { 1581 bottomUpStruct = perfBottomUpStruct; 1582 sameSymbolMap.set(symbolKey, bottomUpStruct); 1583 currentLevelData.push(bottomUpStruct); 1584 if (parent) { 1585 parent.addChildren(bottomUpStruct); 1586 } 1587 } 1588 bottomUpStruct.frameChildren?.push(...perfBottomUpStruct.children); 1589 } 1590 1591 for (const data of currentLevelData) { 1592 this.mergeTreeBifurcation(null, data); 1593 data.frameChildren = []; 1594 } 1595 if (reverseTreeArray) { 1596 return currentLevelData; 1597 } else { 1598 return []; 1599 } 1600 } 1601 1602 /** 1603 * copy整体调用链,从栈顶函数一直copy到栈底函数, 1604 * 给Parent设置selfTime,totalTime设置为children的selfTime,totalTime 1605 * */ 1606 private copyParentNode(perfBottomUpStruct: PerfBottomUpStruct, bottomUpStruct: PerfBottomUpStruct): void { 1607 if (bottomUpStruct.parentNode) { 1608 const copyParent = new PerfBottomUpStruct(bottomUpStruct.parentNode.symbolName); 1609 copyParent.selfTime = perfBottomUpStruct.selfTime; 1610 copyParent.totalTime = perfBottomUpStruct.totalTime; 1611 copyParent.eventCount = perfBottomUpStruct.eventCount; 1612 copyParent.tsArray = [...perfBottomUpStruct.tsArray]; 1613 perfBottomUpStruct.addChildren(copyParent); 1614 this.copyParentNode(copyParent, bottomUpStruct.parentNode); 1615 } 1616 } 1617} 1618 1619export class PerfFile { 1620 fileId: number = 0; 1621 symbol: string = ''; 1622 path: string = ''; 1623 fileName: string = ''; 1624 1625 static setFileName(data: PerfFile): void { 1626 if (data.path) { 1627 let number = data.path.lastIndexOf('/'); 1628 if (number > 0) { 1629 data.fileName = data.path.substring(number + 1); 1630 return; 1631 } 1632 } 1633 data.fileName = data.path; 1634 } 1635 1636 setFileName(): void { 1637 if (this.path) { 1638 let number = this.path.lastIndexOf('/'); 1639 if (number > 0) { 1640 this.fileName = this.path.substring(number + 1); 1641 return; 1642 } 1643 } 1644 this.fileName = this.path; 1645 } 1646} 1647 1648export class PerfThread { 1649 tid: number = 0; 1650 pid: number = 0; 1651 threadName: string = ''; 1652 processName: string = ''; 1653} 1654 1655export class PerfCallChain { 1656 startNS: number = 0; 1657 dur: number = 0; 1658 sampleId: number = 0; 1659 callChainId: number = 0; 1660 vaddrInFile: number = 0; 1661 offsetToVaddr: number = 0; 1662 tid: number = 0; 1663 pid: number = 0; 1664 name: number | string = 0; 1665 fileName: string = ''; 1666 threadState: string = ''; 1667 fileId: number = 0; 1668 symbolId: number = 0; 1669 path: string = ''; 1670 count: number = 0; 1671 eventCount: number = 0; 1672 parentId: string = ''; //合并之后区分的id 1673 id: string = ''; 1674 topDownMerageId: string = ''; //top down合并使用的id 1675 topDownMerageParentId: string = ''; //top down合并使用的id 1676 bottomUpMerageId: string = ''; //bottom up合并使用的id 1677 bottomUpMerageParentId: string = ''; //bottom up合并使用的id 1678 depth: number = 0; 1679 canCharge: boolean = true; 1680 previousNode: PerfCallChain | undefined = undefined; //将list转换为一个链表结构 1681 nextNode: PerfCallChain | undefined = undefined; 1682 isThread: boolean = false; 1683 isProcess: boolean = false; 1684 isThreadState: boolean = false; 1685 sourceFileId: number = 0; 1686 lineNumber: number = 0; 1687 1688 static setNextNode(currentNode: PerfCallChain, nextNode: PerfCallChain): void { 1689 currentNode.nextNode = nextNode; 1690 nextNode.previousNode = currentNode; 1691 } 1692 1693 static setPreviousNode(currentNode: PerfCallChain, prevNode: PerfCallChain): void { 1694 currentNode.previousNode = prevNode; 1695 prevNode.nextNode = currentNode; 1696 } 1697 1698 static merageCallChain(currentNode: PerfCallChain, callChain: PerfCallChain): void { 1699 currentNode.startNS = callChain.startNS; 1700 currentNode.tid = callChain.tid; 1701 currentNode.pid = callChain.pid; 1702 currentNode.sampleId = callChain.sampleId; 1703 currentNode.dur = callChain.dur; 1704 currentNode.count = callChain.count; 1705 currentNode.eventCount = callChain.eventCount; 1706 } 1707} 1708 1709export class PerfCallChainMerageData extends ChartStruct { 1710 // @ts-ignore 1711 #parentNode: PerfCallChainMerageData | undefined = undefined; 1712 // @ts-ignore 1713 #total = 0; 1714 // @ts-ignore 1715 #totalEvent = 0; 1716 id: string = ''; 1717 parentId: string = ''; 1718 parent: PerfCallChainMerageData | undefined = undefined; 1719 symbolName: string = ''; 1720 symbol: string = ''; 1721 libName: string = ''; 1722 path: string = ''; 1723 weight: string = ''; 1724 weightPercent: string = ''; 1725 selfDur: number = 0; 1726 dur: number = 0; 1727 tid: number = 0; 1728 pid: number = 0; 1729 isStore = 0; 1730 canCharge: boolean = true; 1731 children: PerfCallChainMerageData[] = []; 1732 initChildren: PerfCallChainMerageData[] = []; 1733 type: number = 0; 1734 vaddrInFile: number = 0; 1735 offsetToVaddr: number = 0; 1736 isSelected: boolean = false; 1737 searchShow: boolean = true; 1738 isSearch: boolean = false; 1739 isState: boolean = false; 1740 isReverseFilter: boolean = false; 1741 hiddenArray: Array<string> = []; 1742 set parentNode(data: PerfCallChainMerageData | undefined) { 1743 this.parent = data; 1744 this.#parentNode = data; 1745 } 1746 1747 get parentNode(): PerfCallChainMerageData | undefined { 1748 return this.#parentNode; 1749 } 1750 1751 set total(data: number) { 1752 this.#total = data; 1753 this.weight = `${this.dur}`; 1754 this.weightPercent = `${((this.dur / data) * 100).toFixed(1)}%`; 1755 } 1756 1757 get total(): number { 1758 return this.#total; 1759 } 1760 1761 set totalEvent(data: number) { 1762 this.#totalEvent = data; 1763 this.eventPercent = `${((this.eventCount / data) * 100).toFixed(1)}%`; 1764 } 1765 1766 get totalEvent(): number { 1767 return this.#totalEvent; 1768 } 1769 1770 static merageCallChainSample( 1771 currentNode: PerfCallChainMerageData, 1772 callChain: PerfCallChain, 1773 sample: PerfCountSample, 1774 isEnd: boolean, 1775 lineMap: Map<string, Set<number>> 1776 ): void { 1777 if (currentNode.symbolName === '') { 1778 let symbolName = ''; 1779 if (typeof callChain.name === 'number') { 1780 symbolName = DataCache.getInstance().dataDict.get(callChain.name) || ''; 1781 } else { 1782 symbolName = callChain.name; 1783 } 1784 currentNode.symbol = `${symbolName} ${callChain.fileName ? `(${callChain.fileName})` : ''}`; 1785 currentNode.symbolName = symbolName; 1786 currentNode.pid = sample.pid; 1787 currentNode.tid = sample.tid; 1788 currentNode.libName = callChain.fileName; 1789 currentNode.vaddrInFile = callChain.vaddrInFile; 1790 currentNode.offsetToVaddr = callChain.offsetToVaddr; 1791 currentNode.lib = callChain.fileName; 1792 currentNode.addr = `${'0x'}${callChain.vaddrInFile.toString(16)}`; 1793 currentNode.canCharge = callChain.canCharge; 1794 if (callChain.path) { 1795 currentNode.path = callChain.path; 1796 } 1797 if (callChain.sourceFileId) { 1798 currentNode.sourceFile = DataCache.getInstance().dataDict.get(callChain.sourceFileId) || ''; 1799 const lines = lineMap.get(`${currentNode.sourceFile}_${currentNode.symbolName}`); 1800 if (lines) { 1801 currentNode.lineNumber = lines; 1802 } 1803 } 1804 } 1805 if (isEnd) { 1806 currentNode.selfDur += sample.count; 1807 } 1808 if (callChain.isThread && !currentNode.isThread) { 1809 currentNode.isThread = callChain.isThread; 1810 } 1811 if (callChain.isThreadState && !currentNode.isState) { 1812 currentNode.isState = callChain.isThreadState; 1813 } 1814 currentNode.dur += sample.count; 1815 currentNode.count += sample.count; 1816 currentNode.eventCount += sample.eventCount; 1817 currentNode.tsArray.push(...sample.ts.split(',').map(Number)); 1818 } 1819} 1820 1821export class PerfCountSample { 1822 sampleId: number = 0; 1823 tid: number = 0; 1824 count: number = 0; 1825 threadState: string = ''; 1826 pid: number = 0; 1827 eventCount: number = 0; 1828 ts: string = ''; 1829} 1830 1831export class PerfStack { 1832 symbol: string = ''; 1833 path: string = ''; 1834 fileId: number = 0; 1835 type: number = 0; 1836 vaddrInFile: number = 0; 1837} 1838 1839export class PerfCmdLine { 1840 report_value: string = ''; 1841} 1842 1843class PerfAnalysisSample extends PerfCountSample { 1844 threadName: string; 1845 depth: number; 1846 vaddr_in_file: number; 1847 offset_to_vaddr: number; 1848 processName: string; 1849 libId: number; 1850 libName: string; 1851 symbolId: number; 1852 symbolName: string; 1853 1854 constructor( 1855 threadName: string, 1856 depth: number, 1857 vaddr_in_file: number, 1858 offset_to_vaddr: number, 1859 processName: string, 1860 libId: number, 1861 libName: string, 1862 symbolId: number, 1863 symbolName: string 1864 ) { 1865 super(); 1866 this.threadName = threadName; 1867 this.depth = depth; 1868 this.vaddr_in_file = vaddr_in_file; 1869 this.offset_to_vaddr = offset_to_vaddr; 1870 this.processName = processName; 1871 this.libId = libId; 1872 this.libName = libName; 1873 this.symbolId = symbolId; 1874 this.symbolName = symbolName; 1875 } 1876} 1877 1878export function timeMsFormat2p(ns: number): string { 1879 let currentNs = ns; 1880 let hour1 = 3600_000; 1881 let minute1 = 60_000; 1882 let second1 = 1_000; // 1 second 1883 let perfResult = ''; 1884 if (currentNs >= hour1) { 1885 perfResult += `${Math.floor(currentNs / hour1).toFixed(2)}h`; 1886 return perfResult; 1887 } 1888 if (currentNs >= minute1) { 1889 perfResult += `${Math.floor(currentNs / minute1).toFixed(2)}min`; 1890 return perfResult; 1891 } 1892 if (currentNs >= second1) { 1893 perfResult += `${Math.floor(currentNs / second1).toFixed(2)}s`; 1894 return perfResult; 1895 } 1896 if (currentNs > 0) { 1897 perfResult += `${currentNs.toFixed(2)}ms`; 1898 return perfResult; 1899 } 1900 if (perfResult === '') { 1901 perfResult = '0s'; 1902 } 1903 return perfResult; 1904} 1905 1906class HiPrefSample { 1907 name: string = ''; 1908 depth: number = 0; 1909 callchain_id: number = 0; 1910 totalTime: number = 0; 1911 thread_id: number = 0; 1912 id: number = 0; 1913 eventCount: number = 0; 1914 startTime: number = 0; 1915 endTime: number = 0; 1916 timeTip: number = 0; 1917 cpu_id: number = 0; 1918 stack?: Array<HiPerfSymbol>; 1919} 1920