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 convertJSON, 18 DataCache, 19 FileCallChain, 20 getByteWithUnit, 21 getProbablyTime, 22 getTimeString, 23 LogicHandler, 24 MerageBean, 25 merageBeanDataSplit, 26 postMessage, 27 setFileName, 28} from './ProcedureLogicWorkerCommon'; 29 30export let FILE_TYPE_MAP = { 31 '0': 'OPEN', 32 '1': 'CLOSE', 33 '2': 'READ', 34 '3': 'WRITE', 35}; 36 37export let DISKIO_TYPE_MAP = { 38 '1': 'DATA_READ', 39 '2': 'DATA_WRITE', 40 '3': 'METADATA_READ', 41 '4': 'METADATA_WRITE', 42 '5': 'PAGE_IN', 43 '6': 'PAGE_OUT', 44}; 45 46export let VM_TYPE_MAP = { 47 '1': 'File Backed In', 48 '2': 'Page Cache Hit', 49 '3': 'Swap From Zram', 50 '4': 'Swap From Disk', 51 '5': 'Zero Fill Page', 52 '6': 'Zero FAKE Page', 53 '7': 'Copy On Write', 54}; 55 56const FS_TYPE = 0; 57const PF_TYPE = 1; 58const BIO_TYPE = 2; 59 60export class ProcedureLogicWorkerFileSystem extends LogicHandler { 61 private dataCache: DataCache = DataCache.getInstance(); 62 handlerMap: Map<string, any> = new Map<string, any>(); 63 currentEventId: string = ''; 64 tab: string = ''; 65 isAnalysis: boolean = false; 66 private lib: object | undefined; 67 private symbol: object | undefined; 68 private isTopDown: boolean = true; 69 70 handle(data: any): void { 71 if (data.id) { 72 this.currentEventId = data.id; 73 for (let handle of this.handlerMap.values()) { 74 handle.setEventId(this.currentEventId); 75 } 76 } 77 if (data && data.type) { 78 switch (data.type) { 79 case 'fileSystem-init': 80 this.initCallchains(); 81 break; 82 case 'fileSystem-queryCallchains': 83 this.fileSystemQueryCallchains(data); 84 break; 85 case 'fileSystem-queryFileSamples': 86 this.fileSystemQueryFileSamples(data); 87 break; 88 case 'fileSystem-queryIoSamples': 89 this.fileSystemQueryIoSamples(data); 90 break; 91 case 'fileSystem-queryVirtualMemorySamples': 92 this.fileSystemQueryVirtualMemorySamples(data); 93 break; 94 case 'fileSystem-action': 95 this.fileSystemAction(data); 96 break; 97 case 'fileSystem-queryStack': 98 this.fileSystemQueryStack(data); 99 break; 100 case 'fileSystem-queryFileSysEvents': 101 this.fileSystemQueryFileSysEvents(data); 102 break; 103 case 'fileSystem-queryVMEvents': 104 this.fileSystemQueryVMEvents(data); 105 break; 106 case 'fileSystem-queryIOEvents': 107 this.fileSystemQueryIOEvents(data); 108 break; 109 case 'fileSystem-reset': 110 this.fileSystemReset(); 111 break; 112 } 113 } 114 } 115 116 private fileSystemQueryCallchains(data: any): void { 117 let callChains = convertJSON(data.params.list) || []; 118 this.dataCache.clearEBpf(); 119 this.initCallChainTopDown(callChains); 120 // @ts-ignore 121 self.postMessage({ 122 id: data.id, 123 action: 'fileSystem-init', 124 results: [], 125 }); 126 } 127 private fileSystemQueryFileSamples(data: any): void { 128 this.handlerMap.get('fileSystem').samplesList = convertJSON(data.params.list) || []; 129 let fsResults; 130 if (this.isAnalysis) { 131 this.isAnalysis = false; 132 self.postMessage({ 133 id: this.currentEventId, 134 action: data.action, 135 results: this.fileSystemAnalysis(FS_TYPE, this.handlerMap.get('fileSystem').samplesList), 136 }); 137 } else { 138 if (this.lib) { 139 let samplesList = this.fileSystemAnalysis(FS_TYPE, this.handlerMap.get('fileSystem').samplesList, this.lib); 140 this.handlerMap.get('fileSystem').freshCurrentCallChains(samplesList, this.isTopDown); 141 fsResults = this.handlerMap.get('fileSystem').allProcess; 142 this.lib = undefined; 143 } else if (this.symbol) { 144 let samplesList = this.fileSystemAnalysis(FS_TYPE, this.handlerMap.get('fileSystem').samplesList, this.symbol); 145 this.handlerMap.get('fileSystem').freshCurrentCallChains(samplesList, this.isTopDown); 146 fsResults = this.handlerMap.get('fileSystem').allProcess; 147 this.symbol = undefined; 148 } else { 149 fsResults = this.handlerMap.get('fileSystem').resolvingAction([ 150 { 151 funcName: 'getCallChainsBySampleIds', 152 funcArgs: [this.isTopDown], 153 }, 154 ]); 155 } 156 self.postMessage({ 157 id: this.currentEventId, 158 action: data.action, 159 results: fsResults, 160 }); 161 } 162 } 163 private fileSystemQueryIoSamples(data: any): void { 164 this.handlerMap.get('io').samplesList = convertJSON(data.params.list) || []; 165 let ioResults; 166 if (this.isAnalysis) { 167 this.isAnalysis = false; 168 self.postMessage({ 169 id: this.currentEventId, 170 action: data.action, 171 results: this.fileSystemAnalysis(BIO_TYPE, this.handlerMap.get('io').samplesList), 172 }); 173 } else { 174 if (this.lib) { 175 let samplesList = this.fileSystemAnalysis(BIO_TYPE, this.handlerMap.get('io').samplesList, this.lib); 176 this.handlerMap.get('io').freshCurrentCallChains(samplesList, this.isTopDown); 177 ioResults = this.handlerMap.get('io').allProcess; 178 this.lib = undefined; 179 } else if (this.symbol) { 180 let samplesList = this.fileSystemAnalysis(BIO_TYPE, this.handlerMap.get('io').samplesList, this.symbol); 181 this.handlerMap.get('io').freshCurrentCallChains(samplesList, this.isTopDown); 182 ioResults = this.handlerMap.get('io').allProcess; 183 this.symbol = undefined; 184 } else { 185 ioResults = this.handlerMap.get('io').resolvingAction([ 186 { 187 funcName: 'getCallChainsBySampleIds', 188 funcArgs: [this.isTopDown], 189 }, 190 ]); 191 } 192 self.postMessage({ 193 id: this.currentEventId, 194 action: data.action, 195 results: ioResults, 196 }); 197 } 198 } 199 private fileSystemQueryVirtualMemorySamples(data: any): void { 200 this.handlerMap.get('virtualMemory').samplesList = convertJSON(data.params.list) || []; 201 let vmResults; 202 if (this.isAnalysis) { 203 this.isAnalysis = false; 204 self.postMessage({ 205 id: this.currentEventId, 206 action: data.action, 207 results: this.fileSystemAnalysis(PF_TYPE, this.handlerMap.get('virtualMemory').samplesList), 208 }); 209 } else { 210 if (this.lib) { 211 let samplesList = this.fileSystemAnalysis(PF_TYPE, this.handlerMap.get('virtualMemory').samplesList, this.lib); 212 this.handlerMap.get('virtualMemory').freshCurrentCallChains(samplesList, this.isTopDown); 213 vmResults = this.handlerMap.get('virtualMemory').allProcess; 214 this.lib = undefined; 215 } else if (this.symbol) { 216 let samplesList = this.fileSystemAnalysis( 217 PF_TYPE, 218 this.handlerMap.get('virtualMemory').samplesList, 219 this.symbol 220 ); 221 this.handlerMap.get('virtualMemory').freshCurrentCallChains(samplesList, this.isTopDown); 222 vmResults = this.handlerMap.get('virtualMemory').allProcess; 223 this.symbol = undefined; 224 } else { 225 vmResults = this.handlerMap.get('virtualMemory').resolvingAction([ 226 { 227 funcName: 'getCallChainsBySampleIds', 228 funcArgs: [this.isTopDown], 229 }, 230 ]); 231 } 232 self.postMessage({ 233 id: this.currentEventId, 234 action: data.action, 235 results: vmResults, 236 }); 237 } 238 } 239 private fileSystemAction(data: any): void { 240 if (data.params) { 241 this.isTopDown = false; 242 this.handlerMap.get(data.params.callType).isHideEvent = false; 243 this.handlerMap.get(data.params.callType).isHideThread = false; 244 let filter = data.params.args.filter((item: any) => item.funcName == 'getCurrentDataFromDb'); 245 // 从lib层跳转 246 let libFilter = data.params.args.filter((item: any): boolean => item.funcName === 'showLibLevelData'); 247 // 从fun层跳转 248 let funFilter = data.params.args.filter((item: any): boolean => item.funcName === 'showFunLevelData'); 249 let callChainsFilter = data.params.args.filter( 250 (item: any): boolean => item.funcName === 'getCallChainsBySampleIds' 251 ); 252 callChainsFilter.length > 0 ? (this.isTopDown = callChainsFilter[0].funcArgs[0]) : (this.isTopDown = true); 253 if (libFilter.length !== 0) { 254 this.lib = { 255 libId: libFilter[0].funcArgs[0], 256 libName: libFilter[0].funcArgs[1], 257 }; 258 } else if (funFilter.length !== 0) { 259 this.symbol = { 260 symbolId: funFilter[0].funcArgs[0], 261 symbolName: funFilter[0].funcArgs[1], 262 }; 263 } 264 if (filter.length == 0) { 265 // @ts-ignore 266 self.postMessage({ 267 id: data.id, 268 action: data.action, 269 results: this.handlerMap.get(data.params.callType).resolvingAction(data.params.args), 270 }); 271 } else { 272 if (data.params.isAnalysis) { 273 this.isAnalysis = true; 274 } 275 this.handlerMap.get(data.params.callType).resolvingAction(data.params.args); 276 } 277 } 278 } 279 private fileSystemQueryStack(data: any): void { 280 let res = this.getStacksByCallchainId(data.params.callchainId); 281 self.postMessage({ 282 id: data.id, 283 action: data.action, 284 results: res, 285 }); 286 } 287 private fileSystemQueryFileSysEvents(data: any): void { 288 if (data.params.list) { 289 let res = convertJSON(data.params.list) || []; 290 postMessage(data.id, data.action, this.supplementFileSysEvents(res, this.tab)); 291 } else { 292 this.tab = data.params.tab; 293 this.queryFileSysEvents(data.params.leftNs, data.params.rightNs, data.params.typeArr, data.params.tab); 294 } 295 } 296 private fileSystemQueryVMEvents(data: any): void { 297 if (data.params.list) { 298 let res = convertJSON(data.params.list) || []; 299 postMessage(data.id, data.action, this.supplementVMEvents(res)); 300 } else { 301 this.queryVMEvents(data.params.leftNs, data.params.rightNs, data.params.typeArr); 302 } 303 } 304 private fileSystemQueryIOEvents(data: any): void { 305 if (data.params.list) { 306 let res = convertJSON(data.params.list) || []; 307 postMessage(data.id, data.action, this.supplementIoEvents(res)); 308 } else { 309 this.queryIOEvents(data.params.leftNs, data.params.rightNs, data.params.diskIOipids); 310 } 311 } 312 private fileSystemReset(): void { 313 this.handlerMap.get('fileSystem').isHideEvent = false; 314 this.handlerMap.get('fileSystem').isHideThread = false; 315 } 316 public clearAll(): void { 317 this.dataCache.clearEBpf(); 318 for (let key of this.handlerMap.keys()) { 319 if (this.handlerMap.get(key).clear) { 320 this.handlerMap.get(key).clear(); 321 } 322 } 323 this.handlerMap.clear(); 324 } 325 queryFileSysEvents(leftNs: number, rightNs: number, typeArr: Array<number>, tab: string): void { 326 let types: string = Array.from(typeArr).join(','); 327 let sql: string = ''; 328 if (tab === 'events') { 329 sql = this.queryFileSysEventsSQL1(types); 330 } else if (tab === 'history') { 331 sql = this.queryFileSysEventsSQL2(types); 332 } else { 333 sql = this.queryFileSysEventsSQL3(rightNs); 334 } 335 this.queryData(this.currentEventId, 'fileSystem-queryFileSysEvents', sql, { 336 $leftNS: leftNs, 337 $rightNS: rightNs, 338 }); 339 } 340 341 private queryFileSysEventsSQL1(types: string): string { 342 return `select A.callchain_id as callchainId, 343 (A.start_ts - B.start_ts) as startTs, 344 dur, 345 A.type, 346 ifnull(C.name,'Process') || '[' || C.pid || ']' as process, 347 ifnull(D.name,'Thread') || '[' || D.tid || ']' as thread, 348 first_argument as firstArg, 349 second_argument as secondArg, 350 third_argument as thirdArg, 351 fourth_argument as fourthArg, 352 return_value as returnValue, 353 fd, 354 file_id as fileId, 355 error_code as error 356 from file_system_sample A, trace_range B 357 left join process C on A.ipid = C.id 358 left join thread D on A.itid = D.id 359 where A.type in (${types}) and( (A.end_ts - B.start_ts) between $leftNS and $rightNS ) 360 order by A.end_ts;`; 361 } 362 private queryFileSysEventsSQL2(types: string): string { 363 return `select A.callchain_id as callchainId, 364 (A.start_ts - B.start_ts) as startTs, 365 dur, 366 fd, 367 A.type, 368 A.file_id as fileId, 369 ifnull(C.name,'Process') || '[' || C.pid || ']' as process 370 from file_system_sample A, trace_range B 371 left join process C on A.ipid = C.id 372 where A.type in (${types}) and fd not null and( (A.start_ts - B.start_ts) between $leftNS and $rightNS ) 373 order by A.end_ts;`; 374 } 375 private queryFileSysEventsSQL3(rightNs: number): string { 376 return `select TB.callchain_id as callchainId, 377 (TB.start_ts - TR.start_ts) as startTs, 378 (${rightNs} - TB.start_ts) as dur, 379 TB.fd, 380 TB.type, 381 TB.file_id as fileId, 382 ifnull(TC.name, 'Process') || '[' || TC.pid || ']' as process 383 from ( 384 select fd, ipid, 385 max(case when type = 0 then A.end_ts else 0 end) as openTs, 386 max(case when type = 1 then A.end_ts else 0 end) as closeTs 387 from file_system_sample A 388 where type in (0, 1) and A.end_ts between $leftNS and $rightNS group by fd, ipid 389 ) TA 390 left join file_system_sample TB on TA.fd = TB.fd and TA.ipid = TB.ipid and TA.openTs = TB.end_ts 391 left join process TC on TB.ipid = TC.ipid 392 left join trace_range TR 393 where startTs not null and TB.fd not null and TA.closeTs < TA.openTs 394 order by TB.end_ts;`; 395 } 396 queryVMEvents(leftNs: number, rightNs: number, typeArr: Array<number>): void { 397 let sql = `select 398 A.callchain_id as callchainId, 399 (A.start_ts - B.start_ts) as startTs, 400 dur, 401 addr as address, 402 C.pid, 403 T.tid, 404 size, 405 A.type, 406 ifnull(T.name,'Thread') || '[' || T.tid || ']' as thread, 407 ifnull(C.name,'Process') || '[' || C.pid || ']' as process 408 from paged_memory_sample A,trace_range B 409 left join process C on A.ipid = C.id 410 left join thread T on T.id = A.itid 411 where ( 412 (A.end_ts - B.start_ts) between $leftNS and $rightNS 413 );`; 414 this.queryData(this.currentEventId, 'fileSystem-queryVMEvents', sql, { 415 $leftNS: leftNs, 416 $rightNS: rightNs, 417 }); 418 } 419 420 queryIOEvents(leftNs: number, rightNs: number, diskIOipids: Array<number>): void { 421 let ipidsSql = ''; 422 if (diskIOipids.length > 0) { 423 ipidsSql += `and A.ipid in (${diskIOipids.join(',')})`; 424 } 425 let sql = `select 426 A.callchain_id as callchainId, 427 (A.start_ts - B.start_ts) as startTs, 428 latency_dur as dur, 429 path_id as pathId, 430 dur_per_4k as durPer4k, 431 tier, 432 size, 433 A.type, 434 block_number as blockNumber, 435 T.tid, 436 C.pid, 437 ifnull(T.name,'Thread') || '[' || T.tid || ']' as thread, 438 ifnull(C.name,'Process') || '[' || C.pid || ']' as process 439 from bio_latency_sample A,trace_range B 440 left join process C on A.ipid = C.id 441 left join thread T on T.id = A.itid 442 where ( 443 (A.end_ts - B.start_ts) between $leftNS and $rightNS 444 ) ${ipidsSql};`; 445 this.queryData(this.currentEventId, 'fileSystem-queryIOEvents', sql, { 446 $leftNS: leftNs, 447 $rightNS: rightNs, 448 }); 449 } 450 451 getStacksByCallchainId(id: number): Stack[] { 452 let stacks = this.dataCache.eBpfCallChainsMap.get(id) ?? []; 453 let arr: Array<Stack> = []; 454 for (let s of stacks) { 455 let st: Stack = new Stack(); 456 st.path = (this.dataCache.dataDict?.get(s.pathId) ?? 'Unknown Path').split('/').reverse()[0]; 457 st.symbol = `${s.symbolsId == null ? s.ip : this.dataCache.dataDict?.get(s.symbolsId) ?? ''} (${st.path})`; 458 st.type = st.path.endsWith('.so.1') || st.path.endsWith('.dll') || st.path.endsWith('.so') ? 0 : 1; 459 arr.push(st); 460 } 461 return arr; 462 } 463 464 supplementIoEvents(res: Array<IoCompletionTimes>): IoCompletionTimes[] { 465 return res.map((event): IoCompletionTimes => { 466 if (typeof event.pathId === 'string') { 467 event.pathId = parseInt(event.pathId); 468 } 469 event.startTsStr = getTimeString(event.startTs); 470 event.durPer4kStr = event.durPer4k === 0 ? '-' : getProbablyTime(event.durPer4k); 471 event.sizeStr = getByteWithUnit(event.size); 472 event.durStr = getProbablyTime(event.dur); 473 event.path = event.pathId ? this.dataCache.dataDict?.get(event.pathId) ?? '-' : '-'; 474 // @ts-ignore 475 event.operation = DISKIO_TYPE_MAP[`${event.type}`] || 'UNKNOWN'; 476 let stacks = this.dataCache.eBpfCallChainsMap.get(event.callchainId) || []; 477 if (stacks.length > 0) { 478 let stack = stacks[0]; 479 event.backtrace = [ 480 stack.symbolsId === null ? stack.ip : this.dataCache.dataDict?.get(stack.symbolsId) ?? '', 481 `(${stacks.length} other frames)`, 482 ]; 483 } else { 484 event.backtrace = []; 485 } 486 return event; 487 }); 488 } 489 490 supplementVMEvents(res: Array<VirtualMemoryEvent>): VirtualMemoryEvent[] { 491 return res.map((event): VirtualMemoryEvent => { 492 event.startTsStr = getTimeString(event.startTs); 493 event.sizeStr = getByteWithUnit(event.size * 4096); 494 event.durStr = getProbablyTime(event.dur); 495 // @ts-ignore 496 event.operation = VM_TYPE_MAP[`${event.type}`] || 'UNKNOWNN'; 497 return event; 498 }); 499 } 500 501 supplementFileSysEvents(res: Array<FileSysEvent>, tab: string): FileSysEvent[] { 502 res.map((r): void => { 503 let stacks = this.dataCache.eBpfCallChainsMap.get(r.callchainId); 504 r.startTsStr = getTimeString(r.startTs); 505 r.durStr = getProbablyTime(r.dur); 506 if (tab === 'events') { 507 r.firstArg = r.firstArg ?? '0x0'; 508 r.secondArg = r.secondArg ?? '0x0'; 509 r.thirdArg = r.thirdArg ?? '0x0'; 510 r.fourthArg = r.fourthArg ?? '0x0'; 511 r.returnValue = r.returnValue ?? '0x0'; 512 r.error = r.error ?? '0x0'; 513 r.path = this.dataCache.dataDict?.get(r.fileId) ?? '-'; 514 } 515 // @ts-ignore 516 r.typeStr = FILE_TYPE_MAP[`${r.type}`] ?? ''; 517 if (stacks && stacks.length > 0) { 518 let stack = stacks[0]; 519 r.depth = stacks.length; 520 r.symbol = stack.symbolsId === null ? stack.ip : this.dataCache.dataDict?.get(stack.symbolsId) ?? ''; 521 if (tab !== 'events') { 522 r.path = this.dataCache.dataDict?.get(r.fileId) ?? '-'; 523 } 524 r.backtrace = [r.symbol, `(${r.depth} other frames)`]; 525 } else { 526 r.depth = 0; 527 r.symbol = ''; 528 r.path = ''; 529 r.backtrace = []; 530 } 531 }); 532 return res; 533 } 534 535 initCallchains(): void { 536 if (this.handlerMap.size > 0) { 537 this.handlerMap.forEach((value: any): void => { 538 value.clearAll(); 539 }); 540 this.handlerMap.clear(); 541 } 542 this.handlerMap.set('fileSystem', new FileSystemCallTreeHandler('fileSystem', this.queryData.bind(this))); 543 this.handlerMap.set('io', new FileSystemCallTreeHandler('io', this.queryData.bind(this))); 544 this.handlerMap.set('virtualMemory', new FileSystemCallTreeHandler('virtualMemory', this.queryData.bind(this))); 545 this.queryData( 546 this.currentEventId, 547 'fileSystem-queryCallchains', 548 `select callchain_id as callChainId,depth,symbols_id as symbolsId,file_path_id as pathId,ip from ebpf_callstack`, 549 {} 550 ); 551 } 552 553 initCallChainTopDown(list: any[]): void { 554 const callChainsMap = this.dataCache.eBpfCallChainsMap; 555 list.forEach((callchain: FileCallChain): void => { 556 if (callChainsMap.has(callchain.callChainId)) { 557 callChainsMap.get(callchain.callChainId)!.push(callchain); 558 } else { 559 callChainsMap.set(callchain.callChainId, [callchain]); 560 } 561 }); 562 } 563 564 fileSystemAnalysis(type: number, samplesList: Array<FileSample>, obj?: any): Array<FileAnalysisSample> { 565 let analysisSampleList = new Array<FileAnalysisSample>(); 566 for (let sample of samplesList) { 567 let analysisSample = new FileAnalysisSample(sample); 568 let callChainList = this.dataCache.eBpfCallChainsMap.get(sample.callChainId) || []; 569 if (callChainList.length === 0) { 570 continue; 571 } 572 let depth = callChainList.length - 1; 573 let lastCallChain: FileCallChain | undefined | null; 574 //let lastFilter 575 while (true) { 576 if (depth < 0) { 577 lastCallChain = callChainList[depth]; 578 break; 579 } 580 lastCallChain = callChainList[depth]; 581 let symbolName = this.dataCache.dataDict?.get(lastCallChain.symbolsId); 582 let libPath = this.dataCache.dataDict?.get(lastCallChain.pathId); 583 if ( 584 (type === BIO_TYPE && symbolName?.includes('submit_bio')) || 585 (type !== BIO_TYPE && libPath && (libPath.includes('musl') || libPath.includes('libc++'))) 586 ) { 587 depth--; 588 } else { 589 break; 590 } 591 } 592 this.setAnalysisSample(analysisSample, lastCallChain, callChainList); 593 if ((obj && (obj.libId === analysisSample.libId || obj.symbolId === analysisSample.symbolId)) || !obj) { 594 analysisSampleList.push(analysisSample); 595 } 596 } 597 return analysisSampleList; 598 } 599 private setAnalysisSample( 600 analysisSample: FileAnalysisSample, 601 lastCallChain: FileCallChain, 602 callChainList: FileCallChain[] 603 ): void { 604 if (!lastCallChain) { 605 lastCallChain = callChainList[callChainList.length - 1]; 606 } 607 analysisSample.libId = lastCallChain.pathId; 608 analysisSample.symbolId = lastCallChain.symbolsId; 609 let libPath = this.dataCache.dataDict?.get(analysisSample.libId) || ''; 610 let pathArray = libPath.split('/'); 611 analysisSample.libName = pathArray[pathArray.length - 1]; 612 let symbolName = this.dataCache.dataDict?.get(analysisSample.symbolId); 613 if (!symbolName) { 614 symbolName = lastCallChain.ip + ' (' + analysisSample.libName + ')'; 615 } 616 analysisSample.symbolName = symbolName; 617 } 618} 619 620class FileSystemCallTreeHandler { 621 currentTreeMapData: any = {}; 622 allProcess: FileMerageBean[] = []; 623 dataSource: FileMerageBean[] = []; 624 currentDataType: string = ''; 625 currentTreeList: any[] = []; 626 samplesList: FileSample[] = []; 627 splitMapData: any = {}; 628 searchValue: string = ''; 629 currentEventId: string = ''; 630 isHideThread: boolean = false; 631 isHideEvent: boolean = false; 632 queryData = (eventId: string, action: string, sql: string, args: any) => {}; 633 634 constructor(type: string, queryData: any) { 635 this.currentDataType = type; 636 this.queryData = queryData; 637 } 638 639 clear(): void { 640 this.allProcess.length = 0; 641 this.dataSource.length = 0; 642 this.currentTreeList.length = 0; 643 this.samplesList.length = 0; 644 this.splitMapData = {}; 645 } 646 647 setEventId(eventId: string): void { 648 this.currentEventId = eventId; 649 } 650 queryCallChainsSamples(selectionParam: any, sql?: string): void { 651 switch (this.currentDataType) { 652 case 'fileSystem': 653 this.queryFileSamples(selectionParam, sql); 654 break; 655 case 'io': 656 this.queryIOSamples(selectionParam, sql); 657 break; 658 case 'virtualMemory': 659 this.queryPageFaultSamples(selectionParam, sql); 660 break; 661 } 662 } 663 664 queryFileSamples(selectionParam: any, sql?: string): void { 665 let sqlFilter = ''; 666 if (selectionParam.fileSystemType !== undefined && selectionParam.fileSystemType.length > 0) { 667 sqlFilter += ' and s.type in ('; 668 sqlFilter += selectionParam.fileSystemType.join(','); 669 sqlFilter += ') '; 670 } 671 if (sql) { 672 sqlFilter += sql; 673 } else { 674 if ( 675 selectionParam.diskIOipids.length > 0 && 676 !selectionParam.diskIOLatency && 677 selectionParam.fileSystemType.length === 0 678 ) { 679 sqlFilter += `and s.ipid in (${selectionParam.diskIOipids.join(',')})`; 680 } 681 } 682 this.queryData( 683 this.currentEventId, 684 'fileSystem-queryFileSamples', 685 `select s.start_ts - t.start_ts as ts, s.callchain_id as callChainId,h.tid,h.name as threadName,s.dur,s.type,p.pid,p.name as processName from file_system_sample s,trace_range t 686left join process p on p.id = s.ipid 687left join thread h on h.id = s.itid 688where s.end_ts between ${selectionParam.leftNs} + t.start_ts and ${selectionParam.rightNs} + t.start_ts ${sqlFilter} and callchain_id != -1;`, 689 { 690 $startTime: selectionParam.leftNs, 691 $endTime: selectionParam.rightNs, 692 } 693 ); 694 } 695 696 queryIOSamples(selectionParam: any, sql?: string): void { 697 let sqlFilter = ''; 698 const types: number[] = []; 699 if (selectionParam.diskIOReadIds.length > 0) { 700 types.push(...[1, 3]); 701 } 702 if (selectionParam.diskIOWriteIds.length > 0) { 703 types.push(...[2, 4]); 704 } 705 if (selectionParam.diskIOipids.length > 0) { 706 types.push(...[5, 6]); 707 } 708 if (sql) { 709 sqlFilter = sql; 710 } else { 711 if (selectionParam.diskIOipids.length > 0) { 712 sqlFilter += `and (s.ipid in (${selectionParam.diskIOipids.join(',')}) and s.type in (${types.join(',')})) `; 713 } 714 } 715 716 this.queryData( 717 this.currentEventId, 718 'fileSystem-queryIoSamples', 719 `select s.start_ts - t.start_ts as ts, s.callchain_id as callChainId,h.tid,h.name as threadName,s.latency_dur as dur,s.type,p.pid,p.name as processName from bio_latency_sample s,trace_range t 720left join process p on p.id = s.ipid 721left join thread h on h.id = s.itid 722where s.end_ts between ${selectionParam.leftNs} + t.start_ts and ${selectionParam.rightNs} + t.start_ts ${sqlFilter} and callchain_id != -1;`, 723 { 724 $startTime: selectionParam.leftNs, 725 $endTime: selectionParam.rightNs, 726 } 727 ); 728 } 729 730 queryPageFaultSamples(selectionParam: any, sql?: string): void { 731 let sqlFilter = ''; 732 if (sql) { 733 sqlFilter = sql; 734 } else { 735 if ( 736 selectionParam.diskIOipids.length > 0 && 737 !selectionParam.diskIOLatency && 738 !selectionParam.fileSysVirtualMemory 739 ) { 740 sqlFilter += ` and s.ipid in (${selectionParam.diskIOipids.join(',')})`; 741 } 742 } 743 this.queryData( 744 this.currentEventId, 745 'fileSystem-queryVirtualMemorySamples', 746 `select s.start_ts - t.start_ts as ts, s.callchain_id as callChainId,h.tid,h.name as threadName,s.dur,s.type,p.pid,p.name as processName from paged_memory_sample s,trace_range t 747left join process p on p.id = s.ipid 748left join thread h on h.id = s.itid 749where s.end_ts between ${selectionParam.leftNs} + t.start_ts and ${selectionParam.rightNs} + t.start_ts ${sqlFilter} and callchain_id != -1;`, 750 { 751 $startTime: selectionParam.leftNs, 752 $endTime: selectionParam.rightNs, 753 } 754 ); 755 } 756 757 private freshCurrentCallChains(samples: FileSample[], isTopDown: boolean): void { 758 this.currentTreeMapData = {}; 759 this.currentTreeList = []; 760 this.allProcess = []; 761 this.dataSource = []; 762 let totalCount = 0; 763 samples.forEach((sample: FileSample): void => { 764 totalCount += sample.dur; 765 let callChains = this.createThreadAndType(sample); 766 if (callChains.length === 2) { 767 return; 768 } 769 let topIndex = isTopDown ? 0 : callChains.length - 1; 770 if (callChains.length > 1) { 771 let root = 772 this.currentTreeMapData[callChains[topIndex].symbolsId + '' + callChains[topIndex].pathId + sample.pid]; 773 if (root === undefined) { 774 root = new FileMerageBean(); 775 this.currentTreeMapData[callChains[topIndex].symbolsId + '' + callChains[topIndex].pathId + sample.pid] = 776 root; 777 this.currentTreeList.push(root); 778 } 779 FileMerageBean.merageCallChainSample(root, callChains[topIndex], sample, false); 780 this.merageChildrenByIndex(root, callChains, topIndex, sample, isTopDown); 781 } 782 }); 783 let rootMerageMap = this.mergeNodeData(totalCount); 784 this.handleCurrentTreeList(totalCount); 785 this.allProcess = Object.values(rootMerageMap); 786 } 787 private mergeNodeData(totalCount: number): Map<any, any> { 788 let rootMerageMap: any = {}; 789 Object.values(this.currentTreeMapData).forEach((mergeData: any): void => { 790 if (rootMerageMap[mergeData.pid] === undefined) { 791 let fileMerageBean = new FileMerageBean(); //新增进程的节点数据 792 fileMerageBean.canCharge = false; 793 fileMerageBean.isProcess = true; 794 fileMerageBean.symbolName = mergeData.processName; 795 fileMerageBean.symbol = fileMerageBean.symbolName; 796 fileMerageBean.children.push(mergeData); 797 fileMerageBean.initChildren.push(mergeData); 798 fileMerageBean.dur = mergeData.dur; 799 fileMerageBean.count = mergeData.count; 800 fileMerageBean.total = totalCount; 801 fileMerageBean.tsArray = [...mergeData.tsArray]; 802 fileMerageBean.durArray = [...mergeData.durArray]; 803 rootMerageMap[mergeData.pid] = fileMerageBean; 804 } else { 805 rootMerageMap[mergeData.pid].children.push(mergeData); 806 rootMerageMap[mergeData.pid].initChildren.push(mergeData); 807 rootMerageMap[mergeData.pid].dur += mergeData.dur; 808 rootMerageMap[mergeData.pid].count += mergeData.count; 809 rootMerageMap[mergeData.pid].total = totalCount; 810 for (const ts of mergeData.tsArray) { 811 rootMerageMap[mergeData.pid].tsArray.push(ts); 812 } 813 for (const dur of mergeData.durArray) { 814 rootMerageMap[mergeData.pid].durArray.push(dur); 815 } 816 } 817 mergeData.parentNode = rootMerageMap[mergeData.pid]; //子节点添加父节点的引用 818 }); 819 return rootMerageMap; 820 } 821 private handleCurrentTreeList(totalCount: number) { 822 let id = 0; 823 this.currentTreeList.forEach((currentNode: any): void => { 824 currentNode.total = totalCount; 825 this.setMerageName(currentNode); 826 if (currentNode.id === '') { 827 currentNode.id = id + ''; 828 id++; 829 } 830 if (currentNode.parentNode) { 831 if (currentNode.parentNode.id === '') { 832 currentNode.parentNode.id = id + ''; 833 id++; 834 } 835 currentNode.parentId = currentNode.parentNode.id; 836 } 837 }); 838 } 839 createThreadAndType(sample: FileSample): FileCallChain[] { 840 let typeCallChain = new FileCallChain(); 841 typeCallChain.callChainId = sample.callChainId; 842 let map: any = {}; 843 if (this.currentDataType === 'fileSystem') { 844 map = FILE_TYPE_MAP; 845 } else if (this.currentDataType === 'io') { 846 map = DISKIO_TYPE_MAP; 847 } else if (this.currentDataType === 'virtualMemory') { 848 map = VM_TYPE_MAP; 849 } 850 // @ts-ignore 851 typeCallChain.ip = map[sample.type.toString()] || 'UNKNOWN'; 852 typeCallChain.symbolsId = sample.type; 853 typeCallChain.pathId = -1; 854 let threadCallChain = new FileCallChain(); 855 threadCallChain.callChainId = sample.callChainId; 856 threadCallChain.ip = (sample.threadName || 'Thread') + `-${sample.tid}`; 857 threadCallChain.symbolsId = sample.tid; 858 threadCallChain.isThread = true; 859 threadCallChain.pathId = -1; 860 let list: FileCallChain[] = []; 861 const eBpfCallChainsMap = DataCache.getInstance().eBpfCallChainsMap; 862 if (!this.isHideEvent) { 863 list.push(typeCallChain); 864 } 865 if (!this.isHideThread) { 866 list.push(threadCallChain); 867 } 868 list.push(...(eBpfCallChainsMap.get(sample.callChainId) || [])); 869 return list; 870 } 871 872 merageChildrenByIndex( 873 currentNode: FileMerageBean, 874 callChainDataList: any[], 875 index: number, 876 sample: FileSample, 877 isTopDown: boolean 878 ): void { 879 isTopDown ? index++ : index--; 880 let isEnd = isTopDown ? callChainDataList.length === index + 1 : index === 0; 881 let node: FileMerageBean; 882 if ( 883 currentNode.initChildren.filter((child: any) => { 884 if ( 885 child.ip === callChainDataList[index]?.ip || 886 (child.symbolsId !== null && 887 child.symbolsId === callChainDataList[index]?.symbolsId && 888 child.pathId === callChainDataList[index]?.pathId) 889 ) { 890 node = child; 891 FileMerageBean.merageCallChainSample(child, callChainDataList[index], sample, isEnd); 892 return true; 893 } 894 return false; 895 }).length === 0 896 ) { 897 node = new FileMerageBean(); 898 FileMerageBean.merageCallChainSample(node, callChainDataList[index], sample, isEnd); 899 currentNode.children.push(node); 900 currentNode.initChildren.push(node); 901 this.currentTreeList.push(node); 902 node.parentNode = currentNode; 903 } 904 if (node! && !isEnd) this.merageChildrenByIndex(node, callChainDataList, index, sample, isTopDown); 905 } 906 907 setMerageName(currentNode: FileMerageBean): void { 908 if (currentNode.pathId === -1) { 909 currentNode.canCharge = false; 910 currentNode.symbol = currentNode.ip; 911 currentNode.symbolName = currentNode.symbol; 912 currentNode.libName = ''; 913 currentNode.path = ''; 914 } else { 915 const dataCache = DataCache.getInstance(); 916 currentNode.symbol = dataCache.dataDict?.get(currentNode.symbolsId) || currentNode.ip || 'unknown'; 917 currentNode.path = dataCache.dataDict?.get(currentNode.pathId) || 'unknown'; 918 currentNode.libName = setFileName(currentNode.path); 919 currentNode.lib = currentNode.libName; 920 currentNode.addr = currentNode.ip; 921 currentNode.symbolName = `${currentNode.symbol} (${currentNode.libName})`; 922 } 923 } 924 public resolvingAction(params: any[]): FileMerageBean[] { 925 if (params.length > 0) { 926 params.forEach((paramItem: any): void => { 927 if (paramItem.funcName && paramItem.funcArgs) { 928 this.handleDataByFuncName(paramItem.funcName, paramItem.funcArgs); 929 } 930 }); 931 this.dataSource = this.allProcess.filter((process: FileMerageBean): boolean => { 932 return process.children && process.children.length > 0; 933 }); 934 } 935 return this.dataSource; 936 } 937 private handleDataByFuncName(funcName: any, args: any): void { 938 switch (funcName) { 939 case 'getCallChainsBySampleIds': 940 this.freshCurrentCallChains(this.samplesList, args[0]); 941 break; 942 case 'getCurrentDataFromDb': 943 this.getCurrentDataFromDb(args); 944 break; 945 case 'hideSystemLibrary': 946 merageBeanDataSplit.hideSystemLibrary(this.allProcess, this.splitMapData); 947 break; 948 case 'hideNumMaxAndMin': 949 merageBeanDataSplit.hideNumMaxAndMin(this.allProcess, this.splitMapData, args[0], args[1]); 950 break; 951 case 'hideThread': 952 this.isHideThread = args[0]; 953 break; 954 case 'hideEvent': 955 this.isHideEvent = args[0]; 956 break; 957 case 'splitAllProcess': 958 merageBeanDataSplit.splitAllProcess(this.allProcess, this.splitMapData, args[0]); 959 break; 960 case 'resetAllNode': 961 merageBeanDataSplit.resetAllNode(this.allProcess, this.currentTreeList, this.searchValue); 962 break; 963 case 'resotreAllNode': 964 merageBeanDataSplit.resotreAllNode(this.splitMapData, args[0]); 965 break; 966 case 'clearSplitMapData': 967 this.clearSplitMapData(args[0]); 968 break; 969 case 'splitTree': 970 let map = this.splitMapData; 971 let list = this.currentTreeList; 972 merageBeanDataSplit.splitTree(map, this.allProcess, args[0], args[1], args[2], list, this.searchValue); 973 break; 974 case 'setSearchValue': 975 this.searchValue = args[0]; 976 break; 977 } 978 } 979 private getCurrentDataFromDb(args: Array<any>): void { 980 if (args[1]) { 981 let sql = this.setSQLCondition(args[1]); 982 this.queryCallChainsSamples(args[0], sql); 983 } else { 984 this.queryCallChainsSamples(args[0]); 985 } 986 } 987 private setSQLCondition(funcArgs: any): string { 988 let sql = ''; 989 if (funcArgs.processId !== undefined) { 990 sql += `and p.pid = ${funcArgs.processId}`; 991 } 992 if (funcArgs.typeId !== undefined) { 993 sql += ` and s.type = ${funcArgs.typeId}`; 994 } 995 if (funcArgs.threadId !== undefined) { 996 sql += ` and h.tid = ${funcArgs.threadId}`; 997 } 998 return sql; 999 } 1000 clearAll() { 1001 this.samplesList = []; 1002 this.splitMapData = {}; 1003 this.currentTreeMapData = {}; 1004 this.currentTreeList = []; 1005 this.searchValue = ''; 1006 this.allProcess = []; 1007 this.dataSource = []; 1008 this.splitMapData = {}; 1009 this.currentDataType = ''; 1010 } 1011 1012 clearSplitMapData(symbolName: string): void { 1013 delete this.splitMapData[symbolName]; 1014 } 1015} 1016 1017class FileSample { 1018 type: number = 0; 1019 callChainId: number = 0; 1020 dur: number = 0; 1021 pid: number = 0; 1022 tid: number = 0; 1023 threadName: string = ''; 1024 processName: string = ''; 1025 ts: number = 0; 1026} 1027 1028class FileAnalysisSample extends FileSample { 1029 libId = 0; 1030 symbolId = 0; 1031 libName = ''; 1032 symbolName = ''; 1033 constructor(fileSample: FileSample) { 1034 super(); 1035 this.type = fileSample.type; 1036 this.callChainId = fileSample.callChainId; 1037 this.dur = fileSample.dur; 1038 this.pid = fileSample.pid; 1039 this.tid = fileSample.tid; 1040 this.threadName = fileSample.threadName; 1041 this.processName = fileSample.processName; 1042 } 1043} 1044 1045export class FileMerageBean extends MerageBean { 1046 ip: string = ''; 1047 symbolsId: number = 0; 1048 pathId: number = 0; 1049 processName: string = ''; 1050 type: number = 0; 1051 1052 static merageCallChainSample( 1053 currentNode: FileMerageBean, 1054 callChain: FileCallChain, 1055 sample: FileSample, 1056 isEnd: boolean 1057 ) { 1058 if (currentNode.processName === '') { 1059 currentNode.ip = callChain.ip; 1060 currentNode.pid = sample.pid; 1061 currentNode.canCharge = true; 1062 currentNode.pathId = callChain.pathId; 1063 currentNode.symbolsId = callChain.symbolsId; 1064 currentNode.processName = `${sample.processName || 'Process'} ${sample.pid})`; 1065 } 1066 if (isEnd) { 1067 currentNode.selfDur += sample.dur; 1068 currentNode.self = getProbablyTime(currentNode.selfDur); 1069 } 1070 if (callChain.isThread && !currentNode.isThread) { 1071 currentNode.isThread = callChain.isThread; 1072 } 1073 currentNode.dur += sample.dur; 1074 currentNode.count++; 1075 currentNode.tsArray.push(sample.ts); 1076 currentNode.durArray.push(sample.dur); 1077 } 1078} 1079 1080export class Stack { 1081 type: number = 0; 1082 symbol: string = ''; 1083 path: string = ''; 1084} 1085 1086export class FileSysEvent { 1087 isSelected: boolean = false; 1088 id: number = 0; 1089 callchainId: number = 0; 1090 startTs: number = 0; 1091 startTsStr: string = ''; 1092 durStr: string = ''; 1093 dur: number = 0; 1094 process: string = ''; 1095 thread: string = ''; 1096 type: number = 0; 1097 typeStr: string = ''; 1098 fd: number = 0; 1099 size: number = 0; 1100 depth: number = 0; 1101 firstArg: string = ''; 1102 secondArg: string = ''; 1103 thirdArg: string = ''; 1104 fourthArg: string = ''; 1105 returnValue: string = ''; 1106 error: string = ''; 1107 path: string = ''; 1108 symbol: string = ''; 1109 backtrace: Array<string> = []; 1110 fileId: number = 0; 1111} 1112 1113export class IoCompletionTimes { 1114 isSelected: boolean = false; 1115 type: number = 0; 1116 callchainId: number = 0; 1117 startTs: number = 0; 1118 startTsStr: string = ''; 1119 durStr: string = ''; 1120 dur: number = 0; 1121 tid: number = 0; 1122 pid: number = 0; 1123 process: string = ''; 1124 thread: string = ''; 1125 path: string = ''; 1126 pathId: number = 0; 1127 operation: string = ''; 1128 size: number = 0; 1129 sizeStr: string = ''; 1130 blockNumber: string = ''; 1131 tier: number = 0; 1132 backtrace: Array<string> = []; 1133 durPer4kStr: string = ''; 1134 durPer4k: number = 0; 1135} 1136 1137export class VirtualMemoryEvent { 1138 isSelected: boolean = false; 1139 callchainId: number = 0; 1140 startTs: number = 0; 1141 startTsStr: string = ''; 1142 durStr: string = ''; 1143 dur: number = 0; 1144 process: string = ''; 1145 thread: string = ''; 1146 address: string = ''; 1147 size: number = 0; 1148 sizeStr: string = ''; 1149 type: number = 0; 1150 tid: number = 0; 1151 pid: number = 0; 1152 operation: string = ''; 1153} 1154