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