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 { LogicHandler, ChartStruct, convertJSON, DataCache, HiPerfSymbol } from './ProcedureLogicWorkerCommon'; 17import { PerfBottomUpStruct } from '../../bean/PerfBottomUpStruct'; 18 19const systemRuleName: string = '/system/'; 20const numRuleName: string = '/max/min/'; 21const maxDepth: number = 256; 22 23export class ProcedureLogicWorkerPerf extends LogicHandler { 24 filesData: any = {}; 25 samplesData: any = {}; 26 threadData: any = {}; 27 callChainData: any = {}; 28 splitMapData: any = {}; 29 currentTreeMapData: any = {}; 30 currentTreeList: any[] = []; 31 searchValue: string = ''; 32 dataSource: PerfCallChainMerageData[] = []; 33 allProcess: PerfCallChainMerageData[] = []; 34 currentEventId: string = ''; 35 isAnalysis: boolean = false; 36 isPerfBottomUp: boolean = false; 37 isHideThread: boolean = false; 38 isHideThreadState: boolean = false; 39 private lib: object | undefined; 40 private symbol: object | undefined; 41 perfCallData: any[] = []; 42 private dataCache = DataCache.getInstance(); 43 private isTopDown: boolean = true; 44 45 handle(data: any): void { 46 this.currentEventId = data.id; 47 if (data && data.type) { 48 switch (data.type) { 49 case 'perf-init': 50 this.dataCache.perfCountToMs = data.params.fValue; 51 this.initPerfFiles(); 52 break; 53 case 'perf-queryPerfFiles': 54 this.perfQueryPerfFiles(data.params.list); 55 break; 56 case 'perf-queryPerfThread': 57 this.perfQueryPerfThread(data.params.list); 58 break; 59 case 'perf-queryPerfCalls': 60 this.perfQueryPerfCalls(data.params.list); 61 break; 62 case 'perf-queryPerfCallchains': 63 this.perfQueryPerfCallchains(data); 64 break; 65 case 'perf-queryCallchainsGroupSample': 66 this.perfQueryCallchainsGroupSample(data); 67 break; 68 case 'perf-action': 69 this.perfAction(data); 70 break; 71 case 'perf-reset': 72 this.perfReset(); 73 } 74 } 75 } 76 private perfQueryPerfFiles(list: Array<any>): void { 77 let files = convertJSON(list) || []; 78 files.forEach((file: any) => { 79 this.filesData[file.fileId] = this.filesData[file.fileId] || []; 80 PerfFile.setFileName(file); 81 this.filesData[file.fileId].push(file); 82 }); 83 this.initPerfThreads(); 84 } 85 private perfQueryPerfThread(list: Array<any>): void { 86 let threads = convertJSON(list) || []; 87 threads.forEach((thread: any): void => { 88 this.threadData[thread.tid] = thread; 89 }); 90 this.initPerfCalls(); 91 } 92 private perfQueryPerfCalls(list: Array<any>): void { 93 let perfCalls = convertJSON(list) || []; 94 if (perfCalls.length !== 0) { 95 perfCalls.forEach((perfCall: any): void => { 96 this.dataCache.perfCallChainMap.set(perfCall.sampleId, perfCall); 97 }); 98 } 99 this.initPerfCallchains(); 100 } 101 private perfQueryPerfCallchains(data: any): void { 102 let arr = convertJSON(data.params.list) || []; 103 this.initPerfCallChainTopDown(arr); 104 // @ts-ignore 105 self.postMessage({ 106 id: data.id, 107 action: data.action, 108 results: this.dataCache.perfCallChainMap, 109 }); 110 } 111 private perfQueryCallchainsGroupSample(data: any): void { 112 this.samplesData = convertJSON(data.params.list) || []; 113 let result; 114 if (this.isAnalysis) { 115 result = this.resolvingAction([ 116 { 117 funcName: 'combineAnalysisCallChain', 118 funcArgs: [true], 119 }, 120 ]); 121 } else if (this.isPerfBottomUp) { 122 result = this.resolvingAction([ 123 { 124 funcName: 'getBottomUp', 125 funcArgs: [true], 126 }, 127 ]); 128 } else { 129 if (this.lib) { 130 let libData = this.combineCallChainForAnalysis(this.lib); 131 this.freshPerfCallchains(libData, this.isTopDown); 132 result = this.allProcess; 133 this.lib = undefined; 134 } else if (this.symbol) { 135 let funData = this.combineCallChainForAnalysis(this.symbol); 136 this.freshPerfCallchains(funData, this.isTopDown); 137 result = this.allProcess; 138 this.symbol = undefined; 139 } else { 140 result = this.resolvingAction([ 141 { 142 funcName: 'getCallChainsBySampleIds', 143 funcArgs: [this.isTopDown], 144 }, 145 ]); 146 } 147 } 148 self.postMessage({ 149 id: data.id, 150 action: data.action, 151 results: result, 152 }); 153 if (this.isAnalysis) { 154 this.isAnalysis = false; 155 } 156 } 157 private perfAction(data: any): void { 158 if (data.params) { 159 let filter = data.params.filter((item: any): boolean => item.funcName === 'getCurrentDataFromDb'); 160 let libFilter = data.params.filter((item: any): boolean => item.funcName === 'showLibLevelData'); 161 let funFilter = data.params.filter((item: any): boolean => item.funcName === 'showFunLevelData'); 162 if (libFilter.length !== 0) { 163 this.setLib(libFilter); 164 } 165 if (funFilter.length !== 0) { 166 this.setSymbol(funFilter); 167 } 168 if (filter.length === 0) { 169 let result = this.calReturnData(data.params); 170 self.postMessage({ 171 id: data.id, 172 action: data.action, 173 results: result, 174 }); 175 } else { 176 this.resolvingAction(data.params); 177 } 178 } 179 } 180 private perfReset(): void { 181 this.isHideThread = false; 182 this.isHideThreadState = false; 183 } 184 private setLib(libFilter: any): void { 185 this.lib = { 186 libId: libFilter[0].funcArgs[0], 187 libName: libFilter[0].funcArgs[1], 188 }; 189 } 190 private setSymbol(funFilter: any): void { 191 this.symbol = { 192 symbolId: funFilter[0].funcArgs[0], 193 symbolName: funFilter[0].funcArgs[1], 194 }; 195 } 196 private calReturnData(params: any): Array<any> { 197 let result; 198 let callChainsFilter = params.filter((item: any): boolean => item.funcName === 'getCallChainsBySampleIds'); 199 callChainsFilter.length > 0 ? (this.isTopDown = callChainsFilter[0].funcArgs[0]) : (this.isTopDown = true); 200 let isHideSystemSoFilter = params.filter((item: any): boolean => item.funcName === 'hideSystemLibrary'); 201 let hideThreadFilter = params.filter((item: any): boolean => item.funcName === 'hideThread'); 202 let hideThreadStateFilter = params.filter((item: any): boolean => item.funcName === 'hideThreadState'); 203 if (this.lib) { 204 if ( 205 callChainsFilter.length > 0 || 206 isHideSystemSoFilter.length > 0 || 207 hideThreadFilter.length > 0 || 208 hideThreadStateFilter.length > 0 209 ) { 210 this.samplesData = this.combineCallChainForAnalysis(this.lib); 211 result = this.resolvingAction(params); 212 } else { 213 let libData = this.combineCallChainForAnalysis(this.lib); 214 this.freshPerfCallchains(libData, this.isTopDown); 215 result = this.allProcess; 216 this.lib = undefined; 217 } 218 } else if (this.symbol) { 219 if ( 220 callChainsFilter.length > 0 || 221 isHideSystemSoFilter.length > 0 || 222 hideThreadFilter.length > 0 || 223 hideThreadStateFilter.length > 0 224 ) { 225 this.samplesData = this.combineCallChainForAnalysis(this.symbol); 226 result = this.resolvingAction(params); 227 } else { 228 let funData = this.combineCallChainForAnalysis(this.symbol); 229 this.freshPerfCallchains(funData, this.isTopDown); 230 result = this.allProcess; 231 this.symbol = undefined; 232 } 233 } else { 234 result = this.resolvingAction(params); 235 } 236 return result; 237 } 238 initPerfFiles(): void { 239 this.clearAll(); 240 this.queryData( 241 this.currentEventId, 242 'perf-queryPerfFiles', 243 `select file_id as fileId, symbol, path 244 from perf_files`, 245 {} 246 ); 247 } 248 249 initPerfThreads(): void { 250 this.queryData( 251 this.currentEventId, 252 'perf-queryPerfThread', 253 `select a.thread_id as tid, a.thread_name as threadName, a.process_id as pid, b.thread_name as processName 254 from perf_thread a 255 left join (select * from perf_thread where thread_id = process_id) b on a.process_id = b.thread_id`, 256 {} 257 ); 258 } 259 260 initPerfCalls(): void { 261 this.queryData( 262 this.currentEventId, 263 'perf-queryPerfCalls', 264 `select count(callchain_id) as depth, callchain_id as sampleId, name 265 from perf_callchain 266 where callchain_id != -1 267 group by callchain_id`, 268 {} 269 ); 270 } 271 272 initPerfCallchains(): void { 273 this.queryData( 274 this.currentEventId, 275 'perf-queryPerfCallchains', 276 `select c.name, 277 c.callchain_id as sampleId, 278 c.vaddr_in_file as vaddrInFile, 279 c.file_id as fileId, 280 c.depth, 281 c.symbol_id as symbolId 282 from perf_callchain c 283 where callchain_id != -1;`, 284 {} 285 ); 286 } 287 /** 288 * 289 * @param selectionParam 290 * @param sql 从饼图进程或者线程层点击进入Perf Profile时传入 291 */ 292 private getCurrentDataFromDb(selectionParam: any, sql?: string): void { 293 let filterSql = this.setFilterSql(selectionParam, sql); 294 this.queryData( 295 this.currentEventId, 296 'perf-queryCallchainsGroupSample', 297 `select p.callchain_id as sampleId, 298 p.thread_state as threadState, 299 p.thread_id as tid, 300 p.count as count, 301 p.process_id as pid, 302 p.event_count as eventCount, 303 p.ts as ts, 304 p.event_type_id as eventTypeId 305 from (select callchain_id, s.thread_id, s.event_type_id, thread_state, process_id, 306 count(callchain_id) as count,SUM(event_count) as event_count, 307 group_concat(s.timestamp_trace - t.start_ts,',') as ts 308 from perf_sample s, trace_range t 309 left join perf_thread thread on s.thread_id = thread.thread_id 310 where timestamp_trace between ${selectionParam.leftNs} + t.start_ts 311 and ${selectionParam.rightNs} + t.start_ts 312 and callchain_id != -1 313 and s.thread_id != 0 ${filterSql} 314 group by callchain_id, s.thread_id, thread_state, process_id) p`, 315 { 316 $startTime: selectionParam.leftNs, 317 $endTime: selectionParam.rightNs, 318 $sql: filterSql, 319 } 320 ); 321 } 322 private setFilterSql(selectionParam: any, sql?: string): string { 323 let filterSql = ''; 324 if (sql) { 325 const cpus = selectionParam.perfAll ? [] : selectionParam.perfCpus; 326 const cpuFilter = cpus.length > 0 ? ` and s.cpu_id in (${cpus.join(',')}) ` : ''; 327 let arg = `${sql}${cpuFilter}`.substring(3); 328 filterSql = `and ${arg}`; 329 } else { 330 const cpus = selectionParam.perfAll ? [] : selectionParam.perfCpus; 331 const processes = selectionParam.perfAll ? [] : selectionParam.perfProcess; 332 const threads = selectionParam.perfAll ? [] : selectionParam.perfThread; 333 if (cpus.length !== 0 || processes.length !== 0 || threads.length !== 0) { 334 const cpuFilter = cpus.length > 0 ? `or s.cpu_id in (${cpus.join(',')}) ` : ''; 335 const processFilter = processes.length > 0 ? `or thread.process_id in (${processes.join(',')}) ` : ''; 336 const threadFilter = threads.length > 0 ? `or s.thread_id in (${threads.join(',')})` : ''; 337 let arg = `${cpuFilter}${processFilter}${threadFilter}`.substring(3); 338 filterSql = ` and (${arg})`; 339 } 340 } 341 let eventTypeId = selectionParam.perfEventTypeId; 342 const eventTypeFilter = eventTypeId !== undefined ? ` and s.event_type_id = ${eventTypeId}` : ''; 343 filterSql += eventTypeFilter; 344 return filterSql; 345 } 346 347 clearAll(): void { 348 this.filesData = {}; 349 this.samplesData = {}; 350 this.threadData = {}; 351 this.perfCallData = []; 352 this.callChainData = {}; 353 this.splitMapData = {}; 354 this.currentTreeMapData = {}; 355 this.currentTreeList = []; 356 this.searchValue = ''; 357 this.dataSource = []; 358 this.allProcess = []; 359 this.dataCache.clearPerf(); 360 } 361 362 initPerfCallChainTopDown(callChains: PerfCallChain[]): void { 363 this.callChainData = {}; 364 callChains.forEach((callChain: PerfCallChain, index: number): void => { 365 this.setPerfCallChainFrameName(callChain); 366 this.addPerfGroupData(callChain); 367 let callChainDatum = this.callChainData[callChain.sampleId]; 368 if (callChainDatum.length > 1) { 369 PerfCallChain.setNextNode(callChainDatum[callChainDatum.length - 2], callChainDatum[callChainDatum.length - 1]); 370 } 371 }); 372 } 373 374 setPerfCallChainFrameName(callChain: PerfCallChain): void { 375 //设置调用栈的名称 376 callChain.canCharge = true; 377 if (callChain.symbolId === -1) { 378 if (this.filesData[callChain.fileId] && this.filesData[callChain.fileId].length > 0) { 379 callChain.fileName = this.filesData[callChain.fileId][0].fileName; 380 callChain.path = this.filesData[callChain.fileId][0].path; 381 } else { 382 callChain.fileName = 'unknown'; 383 } 384 } else { 385 if (this.filesData[callChain.fileId] && this.filesData[callChain.fileId].length > callChain.symbolId) { 386 callChain.fileName = this.filesData[callChain.fileId][callChain.symbolId].fileName; 387 callChain.path = this.filesData[callChain.fileId][callChain.symbolId].path; 388 } else { 389 callChain.fileName = 'unknown'; 390 } 391 } 392 } 393 394 addPerfGroupData(callChain: PerfCallChain): void { 395 const currentCallChain = this.callChainData[callChain.sampleId] || []; 396 this.callChainData[callChain.sampleId] = currentCallChain; 397 if (currentCallChain.length > maxDepth) { 398 currentCallChain.splice(0, 1); 399 } 400 currentCallChain.push(callChain); 401 } 402 403 addOtherCallchainsData(countSample: PerfCountSample, list: any[]): void { 404 let threadCallChain = new PerfCallChain(); //新增的线程数据 405 threadCallChain.tid = countSample.tid; 406 threadCallChain.canCharge = false; 407 threadCallChain.isThread = true; 408 threadCallChain.name = `${this.threadData[countSample.tid].threadName || 'Thread'}(${countSample.tid})`; 409 let threadStateCallChain = new PerfCallChain(); //新增的线程状态数据 410 threadStateCallChain.tid = countSample.tid; 411 threadStateCallChain.name = countSample.threadState || 'Unknown State'; 412 threadStateCallChain.fileName = threadStateCallChain.name === '-' ? 'Unknown Thread State' : ''; 413 threadStateCallChain.canCharge = false; 414 if (!this.isHideThreadState) { 415 list.unshift(threadStateCallChain); 416 } 417 if (!this.isHideThread) { 418 list.unshift(threadCallChain); 419 } 420 } 421 422 private freshPerfCallchains(perfCountSamples: PerfCountSample[], isTopDown: boolean): void { 423 this.currentTreeMapData = {}; 424 this.currentTreeList = []; 425 let totalSamplesCount = 0; 426 let totalEventCount = 0; 427 perfCountSamples.forEach((perfSample): void => { 428 totalSamplesCount += perfSample.count; 429 totalEventCount += perfSample.eventCount; 430 if (this.callChainData[perfSample.sampleId] && this.callChainData[perfSample.sampleId].length > 0) { 431 let perfCallChains = [...this.callChainData[perfSample.sampleId]]; 432 this.addOtherCallchainsData(perfSample, perfCallChains); 433 let topIndex = isTopDown ? 0 : perfCallChains.length - 1; 434 if (perfCallChains.length > 0) { 435 let symbolName = this.dataCache.dataDict.get(perfCallChains[topIndex].name) || ''; 436 if (typeof perfCallChains[topIndex].name === 'number') { 437 symbolName = this.dataCache.dataDict.get(perfCallChains[topIndex].name) || ''; 438 } else { 439 symbolName = perfCallChains[topIndex].name; 440 } 441 let perfRootNode = this.currentTreeMapData[symbolName + perfSample.pid]; 442 if (perfRootNode === undefined) { 443 perfRootNode = new PerfCallChainMerageData(); 444 this.currentTreeMapData[symbolName + perfSample.pid] = perfRootNode; 445 this.currentTreeList.push(perfRootNode); 446 } 447 PerfCallChainMerageData.merageCallChainSample(perfRootNode, perfCallChains[topIndex], perfSample, false); 448 this.mergeChildrenByIndex(perfRootNode, perfCallChains, topIndex, perfSample, isTopDown); 449 } 450 } 451 }); 452 let rootMerageMap = this.mergeNodeData(totalEventCount, totalSamplesCount); 453 this.handleCurrentTreeList(totalEventCount, totalSamplesCount); 454 this.allProcess = Object.values(rootMerageMap); 455 } 456 private mergeNodeData(totalEventCount: number, totalSamplesCount: number): Map<any, any> { 457 let rootMerageMap: any = {}; 458 // @ts-ignore 459 Object.values(this.currentTreeMapData).forEach((merageData: any): void => { 460 if (rootMerageMap[merageData.pid] === undefined) { 461 let perfProcessMerageData = new PerfCallChainMerageData(); //新增进程的节点数据 462 perfProcessMerageData.canCharge = false; 463 perfProcessMerageData.symbolName = 464 (this.threadData[merageData.tid].processName || 'Process') + `(${merageData.pid})`; 465 perfProcessMerageData.isProcess = true; 466 perfProcessMerageData.symbol = perfProcessMerageData.symbolName; 467 perfProcessMerageData.tid = merageData.tid; 468 perfProcessMerageData.children.push(merageData); 469 perfProcessMerageData.initChildren.push(merageData); 470 perfProcessMerageData.dur = merageData.dur; 471 perfProcessMerageData.count = merageData.dur; 472 perfProcessMerageData.eventCount = merageData.eventCount; 473 perfProcessMerageData.total = totalSamplesCount; 474 perfProcessMerageData.totalEvent = totalEventCount; 475 perfProcessMerageData.tsArray = [...merageData.tsArray]; 476 rootMerageMap[merageData.pid] = perfProcessMerageData; 477 } else { 478 rootMerageMap[merageData.pid].children.push(merageData); 479 rootMerageMap[merageData.pid].initChildren.push(merageData); 480 rootMerageMap[merageData.pid].dur += merageData.dur; 481 rootMerageMap[merageData.pid].count += merageData.dur; 482 rootMerageMap[merageData.pid].eventCount += merageData.eventCount; 483 rootMerageMap[merageData.pid].total = totalSamplesCount; 484 rootMerageMap[merageData.pid].totalEvent = totalEventCount; 485 for (const ts of merageData.tsArray) { 486 rootMerageMap[merageData.pid].tsArray.push(ts); 487 } 488 } 489 merageData.parentNode = rootMerageMap[merageData.pid]; //子节点添加父节点的引用 490 }); 491 return rootMerageMap; 492 } 493 private handleCurrentTreeList(totalEventCount: number, totalSamplesCount: number): void { 494 let id = 0; 495 this.currentTreeList.forEach((perfTreeNode: any): void => { 496 perfTreeNode.total = totalSamplesCount; 497 perfTreeNode.totalEvent = totalEventCount; 498 if (perfTreeNode.id === '') { 499 perfTreeNode.id = id + ''; 500 id++; 501 } 502 if (perfTreeNode.parentNode) { 503 if (perfTreeNode.parentNode.id === '') { 504 perfTreeNode.parentNode.id = id + ''; 505 id++; 506 } 507 perfTreeNode.parentId = perfTreeNode.parentNode.id; 508 } 509 }); 510 } 511 512 mergeChildrenByIndex( 513 currentNode: PerfCallChainMerageData, 514 callChainDataList: PerfCallChain[], 515 index: number, 516 sample: PerfCountSample, 517 isTopDown: boolean 518 ): void { 519 if ((isTopDown && index >= callChainDataList.length - 1) || (!isTopDown && index <= 0)) { 520 return; 521 } 522 isTopDown ? index++ : index--; 523 let isEnd = isTopDown ? callChainDataList.length === index + 1 : index === 0; 524 let node: PerfCallChainMerageData; 525 if ( 526 currentNode.initChildren.filter((child: PerfCallChainMerageData): boolean => { 527 let name: number | string | undefined = callChainDataList[index].name; 528 if (typeof name === 'number') { 529 name = this.dataCache.dataDict.get(name); 530 } 531 if (child.symbolName === name) { 532 node = child; 533 PerfCallChainMerageData.merageCallChainSample(child, callChainDataList[index], sample, isEnd); 534 return true; 535 } 536 return false; 537 }).length === 0 538 ) { 539 node = new PerfCallChainMerageData(); 540 PerfCallChainMerageData.merageCallChainSample(node, callChainDataList[index], sample, isEnd); 541 currentNode.children.push(node); 542 currentNode.initChildren.push(node); 543 this.currentTreeList.push(node); 544 node.parentNode = currentNode; 545 } 546 if (node! && !isEnd) this.mergeChildrenByIndex(node, callChainDataList, index, sample, isTopDown); 547 } 548 549 //所有的操作都是针对整个树结构的 不区分特定的数据 550 splitPerfTree(samples: PerfCallChainMerageData[], name: string, isCharge: boolean, isSymbol: boolean): void { 551 samples.forEach((process: PerfCallChainMerageData): void => { 552 process.children = []; 553 if (isCharge) { 554 this.recursionPerfChargeInitTree(process, name, isSymbol); 555 } else { 556 this.recursionPerfPruneInitTree(process, name, isSymbol); 557 } 558 }); 559 this.resetAllNode(samples); 560 } 561 562 recursionPerfChargeInitTree(sample: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void { 563 if ((isSymbol && sample.symbolName === symbolName) || (!isSymbol && sample.libName === symbolName)) { 564 (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(sample); 565 sample.isStore++; 566 } 567 if (sample.initChildren.length > 0) { 568 sample.initChildren.forEach((child: PerfCallChainMerageData): void => { 569 this.recursionPerfChargeInitTree(child, symbolName, isSymbol); 570 }); 571 } 572 } 573 574 recursionPerfPruneInitTree(node: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void { 575 if ((isSymbol && node.symbolName === symbolName) || (!isSymbol && node.libName === symbolName)) { 576 (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(node); 577 node.isStore++; 578 this.pruneChildren(node, symbolName); 579 } else if (node.initChildren.length > 0) { 580 node.initChildren.forEach((child): void => { 581 this.recursionPerfPruneInitTree(child, symbolName, isSymbol); 582 }); 583 } 584 } 585 586 //symbol lib prune 587 recursionPruneTree(sample: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void { 588 if ((isSymbol && sample.symbolName === symbolName) || (!isSymbol && sample.libName === symbolName)) { 589 sample.parent && sample.parent.children.splice(sample.parent.children.indexOf(sample), 1); 590 } else { 591 sample.children.forEach((child: PerfCallChainMerageData): void => { 592 this.recursionPruneTree(child, symbolName, isSymbol); 593 }); 594 } 595 } 596 597 recursionChargeByRule( 598 sample: PerfCallChainMerageData, 599 ruleName: string, 600 rule: (node: PerfCallChainMerageData) => boolean 601 ): void { 602 if (sample.initChildren.length > 0) { 603 sample.initChildren.forEach((child): void => { 604 if (rule(child)) { 605 (this.splitMapData[ruleName] = this.splitMapData[ruleName] || []).push(child); 606 child.isStore++; 607 } 608 this.recursionChargeByRule(child, ruleName, rule); 609 }); 610 } 611 } 612 613 pruneChildren(sample: PerfCallChainMerageData, symbolName: string): void { 614 if (sample.initChildren.length > 0) { 615 sample.initChildren.forEach((child: PerfCallChainMerageData): void => { 616 child.isStore++; 617 (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(child); 618 this.pruneChildren(child, symbolName); 619 }); 620 } 621 } 622 623 hideSystemLibrary(): void { 624 this.allProcess.forEach((item: PerfCallChainMerageData): void => { 625 item.children = []; 626 this.recursionChargeByRule(item, systemRuleName, (node: PerfCallChainMerageData): boolean => { 627 return node.path.startsWith(systemRuleName); 628 }); 629 }); 630 } 631 632 hideNumMaxAndMin(startNum: number, endNum: string): void { 633 let max = endNum === '∞' ? Number.POSITIVE_INFINITY : parseInt(endNum); 634 this.allProcess.forEach((item: PerfCallChainMerageData): void => { 635 item.children = []; 636 this.recursionChargeByRule(item, numRuleName, (node: PerfCallChainMerageData): boolean => { 637 return node.dur < startNum || node.dur > max; 638 }); 639 }); 640 } 641 642 clearSplitMapData(symbolName: string): void { 643 delete this.splitMapData[symbolName]; 644 } 645 646 resetAllSymbol(symbols: string[]): void { 647 symbols.forEach((symbol: string): void => { 648 let list = this.splitMapData[symbol]; 649 if (list !== undefined) { 650 list.forEach((item: any): void => { 651 item.isStore--; 652 }); 653 } 654 }); 655 } 656 657 resetAllNode(sample: PerfCallChainMerageData[]): void { 658 this.clearSearchNode(); 659 sample.forEach((process: PerfCallChainMerageData): void => { 660 process.searchShow = true; 661 process.isSearch = false; 662 }); 663 this.resetNewAllNode(sample); 664 if (this.searchValue !== '') { 665 this.findSearchNode(sample, this.searchValue, false); 666 this.resetNewAllNode(sample); 667 } 668 } 669 670 resetNewAllNode(sampleArray: PerfCallChainMerageData[]): void { 671 sampleArray.forEach((process: PerfCallChainMerageData): void => { 672 process.children = []; 673 }); 674 let values = this.currentTreeList.map((item: any): any => { 675 item.children = []; 676 return item; 677 }); 678 values.forEach((sample: any): void => { 679 if (sample.parentNode !== undefined) { 680 if (sample.isStore === 0 && sample.searchShow) { 681 let parentNode = sample.parentNode; 682 while (parentNode !== undefined && !(parentNode.isStore === 0 && parentNode.searchShow)) { 683 parentNode = parentNode.parentNode; 684 } 685 if (parentNode) { 686 sample.currentTreeParentNode = parentNode; 687 parentNode.children.push(sample); 688 } 689 } 690 } 691 }); 692 } 693 694 findSearchNode(sampleArray: PerfCallChainMerageData[], search: string, parentSearch: boolean): void { 695 search = search.toLocaleLowerCase(); 696 sampleArray.forEach((sample: PerfCallChainMerageData): void => { 697 if ((sample.symbol && sample.symbol.toLocaleLowerCase().includes(search)) || parentSearch) { 698 sample.searchShow = true; 699 let parentNode = sample.parent; 700 sample.isSearch = sample.symbol !== undefined && sample.symbol.toLocaleLowerCase().includes(search); 701 while (parentNode !== undefined && !parentNode.searchShow) { 702 parentNode.searchShow = true; 703 parentNode = parentNode.parent; 704 } 705 } else { 706 sample.searchShow = false; 707 sample.isSearch = false; 708 } 709 if (sample.children.length > 0) { 710 this.findSearchNode(sample.children, search, sample.searchShow); 711 } 712 }); 713 } 714 715 clearSearchNode(): void { 716 this.currentTreeList.forEach((sample: any): void => { 717 sample.searchShow = true; 718 sample.isSearch = false; 719 }); 720 } 721 722 splitAllProcess(processArray: any[]): void { 723 processArray.forEach((item: any): void => { 724 this.allProcess.forEach((process): void => { 725 if (item.select === '0') { 726 this.recursionPerfChargeInitTree(process, item.name, item.type === 'symbol'); 727 } else { 728 this.recursionPerfPruneInitTree(process, item.name, item.type === 'symbol'); 729 } 730 }); 731 if (!item.checked) { 732 this.resetAllSymbol([item.name]); 733 } 734 }); 735 } 736 resolvingAction(params: any[]): PerfCallChainMerageData[] | PerfAnalysisSample[] | PerfBottomUpStruct[] { 737 if (params.length > 0) { 738 for (let item of params) { 739 if (item.funcName && item.funcArgs) { 740 let result = this.handleDataByFuncName(item.funcName, item.funcArgs); 741 if (result) { 742 return result; 743 } 744 } 745 } 746 this.dataSource = this.allProcess.filter((process: PerfCallChainMerageData): boolean => { 747 return process.children && process.children.length > 0; 748 }); 749 } 750 return this.dataSource; 751 } 752 private queryDataFromDb(funcArgs: any): void { 753 if (funcArgs[1]) { 754 let sql = ''; 755 if (funcArgs[1].processId !== undefined) { 756 sql += `and thread.process_id = ${funcArgs[1].processId}`; 757 } 758 if (funcArgs[1].threadId !== undefined) { 759 sql += ` and s.thread_id = ${funcArgs[1].threadId}`; 760 } 761 this.getCurrentDataFromDb(funcArgs[0], sql); 762 } else { 763 this.getCurrentDataFromDb(funcArgs[0]); 764 } 765 } 766 private handleDataByFuncName(funcName: string, funcArgs: any): Array<any> | undefined { 767 switch (funcName) { 768 case 'getCallChainsBySampleIds': 769 this.freshPerfCallchains(this.samplesData, funcArgs[0]); 770 break; 771 case 'getCurrentDataFromDb': 772 this.queryDataFromDb(funcArgs); 773 break; 774 case 'hideSystemLibrary': 775 this.hideSystemLibrary(); 776 break; 777 case 'hideThread': 778 this.isHideThread = funcArgs[0]; 779 break; 780 case 'hideThreadState': 781 this.isHideThreadState = funcArgs[0]; 782 break; 783 case 'hideNumMaxAndMin': 784 this.hideNumMaxAndMin(funcArgs[0], funcArgs[1]); 785 break; 786 case 'splitAllProcess': 787 this.splitAllProcess(funcArgs[0]); 788 break; 789 case 'resetAllNode': 790 this.resetAllNode(this.allProcess); 791 break; 792 case 'resotreAllNode': 793 this.resetAllSymbol(funcArgs[0]); 794 break; 795 case 'clearSplitMapData': 796 this.clearSplitMapData(funcArgs[0]); 797 break; 798 case 'splitTree': 799 this.splitPerfTree(this.allProcess, funcArgs[0], funcArgs[1], funcArgs[2]); 800 break; 801 case 'setSearchValue': 802 this.searchValue = funcArgs[0]; 803 break; 804 case 'setCombineCallChain': 805 this.isAnalysis = true; 806 break; 807 case 'setPerfBottomUp': 808 this.isPerfBottomUp = true; 809 break; 810 case 'combineAnalysisCallChain': 811 return this.combineCallChainForAnalysis(); 812 case 'getBottomUp': 813 return this.getBottomUp(); 814 } 815 } 816 817 combineCallChainForAnalysis(obj?: any): PerfAnalysisSample[] { 818 let sampleCallChainList: Array<PerfAnalysisSample> = []; 819 for (let sample of this.samplesData) { 820 let callChains = [...this.callChainData[sample.sampleId]]; 821 const lastCallChain = callChains[callChains.length - 1]; 822 const threadName = this.threadData[sample.tid].threadName || 'Thread'; 823 const processName = this.threadData[sample.pid].threadName || 'Process'; 824 const funcName = this.dataCache.dataDict.get(lastCallChain.name); 825 if ( 826 (obj && obj.libId === lastCallChain.fileId && obj.libName === lastCallChain.fileName) || 827 (obj && obj.symbolId === lastCallChain.symbolId && obj.symbolName === funcName) || 828 !obj 829 ) { 830 let analysisSample = new PerfAnalysisSample( 831 threadName, 832 processName, 833 lastCallChain.fileId, 834 lastCallChain.fileName, 835 lastCallChain.symbolId, 836 this.dataCache.dataDict.get(lastCallChain.name) || '' 837 ); 838 analysisSample.tid = sample.tid; 839 analysisSample.pid = sample.pid; 840 analysisSample.count = sample.count; 841 analysisSample.threadState = sample.threadState; 842 analysisSample.eventCount = sample.eventCount; 843 analysisSample.sampleId = sample.sampleId; 844 sampleCallChainList.push(analysisSample); 845 } 846 } 847 return sampleCallChainList; 848 } 849 850 getBottomUp(): PerfBottomUpStruct[] { 851 const topUp = new PerfBottomUpStruct('topUp'); 852 let perfTime = 1; 853 for (let sample of this.samplesData) { 854 let currentNode = topUp; 855 let callChains = this.callChainData[sample.sampleId]; 856 for (let i = 0; i < callChains.length; i++) { 857 if (i === 0) { 858 currentNode = topUp; 859 } 860 let item = callChains[i]; 861 const existingNode = currentNode.children.find( 862 (child) => child.symbolName === `${item.name}(${item.fileName})` 863 ); 864 if (existingNode) { 865 existingNode.tsArray.push(...sample.ts.split(',').map(Number)); 866 currentNode = existingNode; 867 existingNode.totalTime += perfTime * sample.count; 868 existingNode.eventCount += sample.eventCount; 869 existingNode.calculateSelfTime(); 870 existingNode.notifyParentUpdateSelfTime(); 871 } else { 872 const symbolName = this.dataCache.dataDict.get(item.name) || ''; 873 let newNode = new PerfBottomUpStruct(`${symbolName}(${item.fileName})`); 874 newNode.totalTime = perfTime * sample.count; 875 newNode.eventCount = sample.eventCount; 876 newNode.tsArray = sample.ts.split(',').map(Number); 877 currentNode.addChildren(newNode); 878 newNode.calculateSelfTime(); 879 newNode.notifyParentUpdateSelfTime(); 880 currentNode = newNode; 881 } 882 } 883 } 884 topUp.children.forEach((child: PerfBottomUpStruct): void => { 885 child.parentNode = undefined; 886 }); 887 888 let date = this.topUpDataToBottomUpData(topUp.children); 889 if (this.isPerfBottomUp) { 890 this.isPerfBottomUp = false; 891 } 892 return date; 893 } 894 895 private topUpDataToBottomUpData(perfPositiveArray: Array<PerfBottomUpStruct>): Array<PerfBottomUpStruct> { 896 let reverseTreeArray: Array<PerfBottomUpStruct> = []; 897 const recursionTree = (perfBottomUpStruct: PerfBottomUpStruct): void => { 898 if (perfBottomUpStruct.selfTime > 0) { 899 const clonePerfBottomUpStruct = new PerfBottomUpStruct(perfBottomUpStruct.symbolName); 900 clonePerfBottomUpStruct.selfTime = perfBottomUpStruct.selfTime; 901 clonePerfBottomUpStruct.totalTime = perfBottomUpStruct.totalTime; 902 clonePerfBottomUpStruct.eventCount = perfBottomUpStruct.eventCount; 903 clonePerfBottomUpStruct.tsArray = [...perfBottomUpStruct.tsArray]; 904 reverseTreeArray.push(clonePerfBottomUpStruct); 905 this.copyParentNode(clonePerfBottomUpStruct, perfBottomUpStruct); 906 } 907 if (perfBottomUpStruct.children.length > 0) { 908 for (const children of perfBottomUpStruct.children) { 909 children.parentNode = perfBottomUpStruct; 910 recursionTree(children); 911 } 912 } 913 }; 914 for (const perfBottomUpStruct of perfPositiveArray) { 915 recursionTree(perfBottomUpStruct); 916 } 917 return this.mergeTreeBifurcation(reverseTreeArray, null); 918 } 919 920 private mergeTreeBifurcation( 921 reverseTreeArray: Array<PerfBottomUpStruct> | null, 922 parent: PerfBottomUpStruct | null 923 ): Array<PerfBottomUpStruct> { 924 const sameSymbolMap = new Map<string, PerfBottomUpStruct>(); 925 const currentLevelData: Array<PerfBottomUpStruct> = []; 926 const dataArray = reverseTreeArray || parent?.frameChildren; 927 if (!dataArray) { 928 return []; 929 } 930 for (const perfBottomUpStruct of dataArray) { 931 let symbolKey = perfBottomUpStruct.symbolName; 932 let bottomUpStruct: PerfBottomUpStruct; 933 if (sameSymbolMap.has(symbolKey)) { 934 bottomUpStruct = sameSymbolMap.get(symbolKey)!; 935 bottomUpStruct.totalTime += perfBottomUpStruct.totalTime; 936 bottomUpStruct.selfTime += perfBottomUpStruct.selfTime; 937 for (const ts of perfBottomUpStruct.tsArray) { 938 bottomUpStruct.tsArray.push(ts); 939 } 940 } else { 941 bottomUpStruct = perfBottomUpStruct; 942 sameSymbolMap.set(symbolKey, bottomUpStruct); 943 currentLevelData.push(bottomUpStruct); 944 if (parent) { 945 parent.addChildren(bottomUpStruct); 946 } 947 } 948 bottomUpStruct.frameChildren?.push(...perfBottomUpStruct.children); 949 } 950 951 for (const data of currentLevelData) { 952 this.mergeTreeBifurcation(null, data); 953 data.frameChildren = []; 954 } 955 if (reverseTreeArray) { 956 return currentLevelData; 957 } else { 958 return []; 959 } 960 } 961 962 /** 963 * copy整体调用链,从栈顶函数一直copy到栈底函数, 964 * 给Parent设置selfTime,totalTime设置为children的selfTime,totalTime 965 * */ 966 private copyParentNode(perfBottomUpStruct: PerfBottomUpStruct, bottomUpStruct: PerfBottomUpStruct): void { 967 if (bottomUpStruct.parentNode) { 968 const copyParent = new PerfBottomUpStruct(bottomUpStruct.parentNode.symbolName); 969 copyParent.selfTime = perfBottomUpStruct.selfTime; 970 copyParent.totalTime = perfBottomUpStruct.totalTime; 971 copyParent.eventCount = perfBottomUpStruct.eventCount; 972 copyParent.tsArray = [...perfBottomUpStruct.tsArray]; 973 perfBottomUpStruct.addChildren(copyParent); 974 this.copyParentNode(copyParent, bottomUpStruct.parentNode); 975 } 976 } 977} 978 979export class PerfFile { 980 fileId: number = 0; 981 symbol: string = ''; 982 path: string = ''; 983 fileName: string = ''; 984 985 static setFileName(data: PerfFile): void { 986 if (data.path) { 987 let number = data.path.lastIndexOf('/'); 988 if (number > 0) { 989 data.fileName = data.path.substring(number + 1); 990 return; 991 } 992 } 993 data.fileName = data.path; 994 } 995 996 setFileName(): void { 997 if (this.path) { 998 let number = this.path.lastIndexOf('/'); 999 if (number > 0) { 1000 this.fileName = this.path.substring(number + 1); 1001 return; 1002 } 1003 } 1004 this.fileName = this.path; 1005 } 1006} 1007 1008export class PerfThread { 1009 tid: number = 0; 1010 pid: number = 0; 1011 threadName: string = ''; 1012 processName: string = ''; 1013} 1014 1015export class PerfCallChain { 1016 startNS: number = 0; 1017 dur: number = 0; 1018 sampleId: number = 0; 1019 callChainId: number = 0; 1020 vaddrInFile: number = 0; 1021 tid: number = 0; 1022 pid: number = 0; 1023 name: number | string = 0; 1024 fileName: string = ''; 1025 threadState: string = ''; 1026 fileId: number = 0; 1027 symbolId: number = 0; 1028 path: string = ''; 1029 count: number = 0; 1030 eventCount: number = 0; 1031 parentId: string = ''; //合并之后区分的id 1032 id: string = ''; 1033 topDownMerageId: string = ''; //top down合并使用的id 1034 topDownMerageParentId: string = ''; //top down合并使用的id 1035 bottomUpMerageId: string = ''; //bottom up合并使用的id 1036 bottomUpMerageParentId: string = ''; //bottom up合并使用的id 1037 depth: number = 0; 1038 canCharge: boolean = true; 1039 previousNode: PerfCallChain | undefined = undefined; //将list转换为一个链表结构 1040 nextNode: PerfCallChain | undefined = undefined; 1041 isThread: boolean = false; 1042 isProcess: boolean = false; 1043 1044 static setNextNode(currentNode: PerfCallChain, nextNode: PerfCallChain): void { 1045 currentNode.nextNode = nextNode; 1046 nextNode.previousNode = currentNode; 1047 } 1048 1049 static setPreviousNode(currentNode: PerfCallChain, prevNode: PerfCallChain): void { 1050 currentNode.previousNode = prevNode; 1051 prevNode.nextNode = currentNode; 1052 } 1053 1054 static merageCallChain(currentNode: PerfCallChain, callChain: PerfCallChain): void { 1055 currentNode.startNS = callChain.startNS; 1056 currentNode.tid = callChain.tid; 1057 currentNode.pid = callChain.pid; 1058 currentNode.sampleId = callChain.sampleId; 1059 currentNode.dur = callChain.dur; 1060 currentNode.count = callChain.count; 1061 currentNode.eventCount = callChain.eventCount; 1062 } 1063} 1064 1065export class PerfCallChainMerageData extends ChartStruct { 1066 // @ts-ignore 1067 #parentNode: PerfCallChainMerageData | undefined = undefined; 1068 // @ts-ignore 1069 #total = 0; 1070 // @ts-ignore 1071 #totalEvent = 0; 1072 id: string = ''; 1073 parentId: string = ''; 1074 parent: PerfCallChainMerageData | undefined = undefined; 1075 symbolName: string = ''; 1076 symbol: string = ''; 1077 libName: string = ''; 1078 path: string = ''; 1079 weight: string = ''; 1080 weightPercent: string = ''; 1081 selfDur: number = 0; 1082 dur: number = 0; 1083 tid: number = 0; 1084 pid: number = 0; 1085 isStore = 0; 1086 canCharge: boolean = true; 1087 children: PerfCallChainMerageData[] = []; 1088 initChildren: PerfCallChainMerageData[] = []; 1089 type: number = 0; 1090 vaddrInFile: number = 0; 1091 isSelected: boolean = false; 1092 searchShow: boolean = true; 1093 isSearch: boolean = false; 1094 set parentNode(data: PerfCallChainMerageData | undefined) { 1095 this.parent = data; 1096 this.#parentNode = data; 1097 } 1098 1099 get parentNode(): PerfCallChainMerageData | undefined { 1100 return this.#parentNode; 1101 } 1102 1103 set total(data: number) { 1104 this.#total = data; 1105 this.weight = `${this.dur}`; 1106 this.weightPercent = `${((this.dur / data) * 100).toFixed(1)}%`; 1107 } 1108 1109 get total(): number { 1110 return this.#total; 1111 } 1112 1113 set totalEvent(data: number) { 1114 this.#totalEvent = data; 1115 this.eventPercent = `${((this.eventCount / data) * 100).toFixed(1)}%`; 1116 } 1117 1118 get totalEvent(): number { 1119 return this.#totalEvent; 1120 } 1121 1122 static merageCallChainSample( 1123 currentNode: PerfCallChainMerageData, 1124 callChain: PerfCallChain, 1125 sample: PerfCountSample, 1126 isEnd: boolean 1127 ): void { 1128 if (currentNode.symbolName === '') { 1129 let symbolName = ''; 1130 if (typeof callChain.name === 'number') { 1131 symbolName = DataCache.getInstance().dataDict.get(callChain.name) || ''; 1132 } else { 1133 symbolName = callChain.name; 1134 } 1135 currentNode.symbol = `${symbolName} ${callChain.fileName ? `(${callChain.fileName})` : ''}`; 1136 currentNode.symbolName = symbolName; 1137 currentNode.pid = sample.pid; 1138 currentNode.tid = sample.tid; 1139 currentNode.libName = callChain.fileName; 1140 currentNode.vaddrInFile = callChain.vaddrInFile; 1141 currentNode.lib = callChain.fileName; 1142 currentNode.addr = `${'0x'}${callChain.vaddrInFile.toString(16)}`; 1143 currentNode.canCharge = callChain.canCharge; 1144 if (callChain.path) { 1145 currentNode.path = callChain.path; 1146 } 1147 } 1148 if (isEnd) { 1149 currentNode.selfDur += sample.count; 1150 } 1151 if (callChain.isThread && !currentNode.isThread) { 1152 currentNode.isThread = callChain.isThread; 1153 } 1154 currentNode.dur += sample.count; 1155 currentNode.count += sample.count; 1156 currentNode.eventCount += sample.eventCount; 1157 currentNode.tsArray.push(...sample.ts.split(',').map(Number)); 1158 } 1159} 1160 1161export class PerfCountSample { 1162 sampleId: number = 0; 1163 tid: number = 0; 1164 count: number = 0; 1165 threadState: string = ''; 1166 pid: number = 0; 1167 eventCount: number = 0; 1168 ts: string = ''; 1169} 1170 1171export class PerfStack { 1172 symbol: string = ''; 1173 path: string = ''; 1174 fileId: number = 0; 1175 type: number = 0; 1176 vaddrInFile: number = 0; 1177} 1178 1179export class PerfCmdLine { 1180 report_value: string = ''; 1181} 1182 1183class PerfAnalysisSample extends PerfCountSample { 1184 threadName: string; 1185 processName: string; 1186 libId: number; 1187 libName: string; 1188 symbolId: number; 1189 symbolName: string; 1190 1191 constructor( 1192 threadName: string, 1193 processName: string, 1194 libId: number, 1195 libName: string, 1196 symbolId: number, 1197 symbolName: string 1198 ) { 1199 super(); 1200 this.threadName = threadName; 1201 this.processName = processName; 1202 this.libId = libId; 1203 this.libName = libName; 1204 this.symbolId = symbolId; 1205 this.symbolName = symbolName; 1206 } 1207} 1208 1209export function timeMsFormat2p(ns: number): string { 1210 let currentNs = ns; 1211 let hour1 = 3600_000; 1212 let minute1 = 60_000; 1213 let second1 = 1_000; // 1 second 1214 let perfResult = ''; 1215 if (currentNs >= hour1) { 1216 perfResult += `${Math.floor(currentNs / hour1).toFixed(2)}h`; 1217 return perfResult; 1218 } 1219 if (currentNs >= minute1) { 1220 perfResult += `${Math.floor(currentNs / minute1).toFixed(2)}min`; 1221 return perfResult; 1222 } 1223 if (currentNs >= second1) { 1224 perfResult += `${Math.floor(currentNs / second1).toFixed(2)}s`; 1225 return perfResult; 1226 } 1227 if (currentNs > 0) { 1228 perfResult += `${currentNs.toFixed(2)}ms`; 1229 return perfResult; 1230 } 1231 if (perfResult === '') { 1232 perfResult = '0s'; 1233 } 1234 return perfResult; 1235} 1236 1237class HiPrefSample { 1238 name: string = ''; 1239 depth: number = 0; 1240 callchain_id: number = 0; 1241 totalTime: number = 0; 1242 thread_id: number = 0; 1243 id: number = 0; 1244 eventCount: number = 0; 1245 startTime: number = 0; 1246 endTime: number = 0; 1247 timeTip: number = 0; 1248 cpu_id: number = 0; 1249 stack?: Array<HiPerfSymbol>; 1250} 1251