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