• 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 {
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