• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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