• 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
16export class ChartStruct {
17  depth: number = 0;
18  symbol: string = '';
19  lib: string = '';
20  addr: string = '';
21  size: number = 0;
22  count: number = 0;
23  eventCount: number = 0;
24  eventPercent: string = '';
25  dur: number = 0;
26  parent: ChartStruct | undefined;
27  children: Array<ChartStruct> = [];
28  isSearch: boolean = false;
29  tsArray: Array<number> = []; // 每个绘制的函数由哪些时间点的样本组成
30  countArray: Array<number> = []; // native hook统计模式下一个时间点有多次分配
31  durArray: Array<number> = [];
32  isThread: boolean = false;
33  isProcess: boolean = false;
34}
35
36export class Msg {
37  tag: string = '';
38  index: number = 0;
39  isSending: boolean = false;
40  data: Array<any> = [];
41}
42
43export class HiPerfSymbol {
44  id: number = 0;
45  startTime: number = 0;
46  eventCount: number = 0;
47  endTime: number = 0;
48  totalTime: number = 0;
49  fileId: number = 0;
50  symbolId: number = 0;
51  cpu_id: number = 0;
52  depth: number = 0;
53  children?: Array<HiPerfSymbol>;
54  callchain_id: number = 0;
55  thread_id: number = 0;
56  name: string = '';
57
58  public clone(): HiPerfSymbol {
59    const cloneSymbol = new HiPerfSymbol();
60    cloneSymbol.children = new Array<HiPerfSymbol>();
61    cloneSymbol.depth = this.depth;
62    return cloneSymbol;
63  }
64}
65
66export class MerageBean extends ChartStruct {
67  #parentNode: MerageBean | undefined = undefined;
68  #total = 0;
69  parent: MerageBean | undefined = undefined;
70  id: string = '';
71  parentId: string = '';
72  symbolName: string = '';
73  symbol: string = '';
74  libName: string = '';
75  path: string = '';
76  self: string = '0s';
77  weight: string = '';
78  weightPercent: string = '';
79  selfDur: number = 0;
80  dur: number = 0;
81  pid: number = 0;
82  canCharge: boolean = true;
83  isStore = 0;
84  isSelected: boolean = false;
85  searchShow: boolean = true;
86  children: MerageBean[] = [];
87  initChildren: MerageBean[] = [];
88  type: number = 0;
89  set parentNode(data: MerageBean | undefined) {
90    this.parent = data;
91    this.#parentNode = data;
92  }
93
94  get parentNode(): MerageBean | undefined {
95    return this.#parentNode;
96  }
97
98  set total(data: number) {
99    this.#total = data;
100    this.weight = `${getProbablyTime(this.dur)}`;
101    this.weightPercent = `${((this.dur / data) * 100).toFixed(1)}%`;
102  }
103
104  get total(): number {
105    return this.#total;
106  }
107}
108
109class MerageBeanDataSplit {
110  systmeRuleName = '/system/';
111  numRuleName = '/max/min/';
112
113  //所有的操作都是针对整个树结构的, 不区分特定的数据
114  splitTree(
115    splitMapData: any,
116    data: MerageBean[],
117    name: string,
118    isCharge: boolean,
119    isSymbol: boolean,
120    currentTreeList: any[],
121    searchValue: string
122  ): void {
123    data.forEach((process) => {
124      process.children = [];
125      if (isCharge) {
126        this.recursionChargeInitTree(splitMapData, process, name, isSymbol);
127      } else {
128        this.recursionPruneInitTree(splitMapData, process, name, isSymbol);
129      }
130    });
131    this.resetAllNode(data, currentTreeList, searchValue);
132  }
133
134  recursionChargeInitTree(splitMapData: any, node: MerageBean, symbolName: string, isSymbol: boolean): void {
135    if ((isSymbol && node.symbolName == symbolName) || (!isSymbol && node.libName == symbolName)) {
136      (splitMapData[symbolName] = splitMapData[symbolName] || []).push(node);
137      node.isStore++;
138    }
139    if (node.initChildren.length > 0) {
140      node.initChildren.forEach((child) => {
141        this.recursionChargeInitTree(splitMapData, child, symbolName, isSymbol);
142      });
143    }
144  }
145
146  recursionPruneInitTree(splitMapData: any, node: MerageBean, symbolName: string, isSymbol: boolean): void {
147    if ((isSymbol && node.symbolName == symbolName) || (!isSymbol && node.libName == symbolName)) {
148      (splitMapData[symbolName] = splitMapData[symbolName] || []).push(node);
149      node.isStore++;
150      this.pruneChildren(splitMapData, node, symbolName);
151    } else if (node.initChildren.length > 0) {
152      node.initChildren.forEach((child) => {
153        this.recursionPruneInitTree(splitMapData, child, symbolName, isSymbol);
154      });
155    }
156  }
157
158  //symbol lib prune
159  recursionPruneTree(node: MerageBean, symbolName: string, isSymbol: boolean): void {
160    if ((isSymbol && node.symbolName == symbolName) || (!isSymbol && node.libName == symbolName)) {
161      node.parent && node.parent.children.splice(node.parent.children.indexOf(node), 1);
162    } else {
163      node.children.forEach((child) => {
164        this.recursionPruneTree(child, symbolName, isSymbol);
165      });
166    }
167  }
168
169  recursionChargeByRule(
170    splitMapData: any,
171    node: MerageBean,
172    ruleName: string,
173    rule: (node: MerageBean) => boolean
174  ): void {
175    if (node.initChildren.length > 0) {
176      node.initChildren.forEach((child) => {
177        if (rule(child)) {
178          (splitMapData[ruleName] = splitMapData[ruleName] || []).push(child);
179          child.isStore++;
180        }
181        this.recursionChargeByRule(splitMapData, child, ruleName, rule);
182      });
183    }
184  }
185
186  pruneChildren(splitMapData: any, node: MerageBean, symbolName: string): void {
187    if (node.initChildren.length > 0) {
188      node.initChildren.forEach((child) => {
189        child.isStore++;
190        (splitMapData[symbolName] = splitMapData[symbolName] || []).push(child);
191        this.pruneChildren(splitMapData, child, symbolName);
192      });
193    }
194  }
195
196  hideSystemLibrary(allProcess: MerageBean[], splitMapData: any): void {
197    allProcess.forEach((item) => {
198      item.children = [];
199      this.recursionChargeByRule(splitMapData, item, this.systmeRuleName, (node) => {
200        return node.path.startsWith(this.systmeRuleName);
201      });
202    });
203  }
204
205  hideNumMaxAndMin(allProcess: MerageBean[], splitMapData: any, startNum: number, endNum: string): void {
206    let max = endNum == '∞' ? Number.POSITIVE_INFINITY : parseInt(endNum);
207    allProcess.forEach((item) => {
208      item.children = [];
209      this.recursionChargeByRule(splitMapData, item, this.numRuleName, (node) => {
210        return node.count < startNum || node.count > max;
211      });
212    });
213  }
214
215  resotreAllNode(splitMapData: any, symbols: string[]): void {
216    symbols.forEach((symbol) => {
217      let list = splitMapData[symbol];
218      if (list != undefined) {
219        list.forEach((item: any) => {
220          item.isStore--;
221        });
222      }
223    });
224  }
225
226  resetAllNode(data: MerageBean[], currentTreeList: any[], searchValue: string): void {
227    this.clearSearchNode(currentTreeList);
228    data.forEach((process) => {
229      process.searchShow = true;
230      process.isSearch = false;
231    });
232    this.resetNewAllNode(data, currentTreeList);
233    if (searchValue != '') {
234      this.findSearchNode(data, searchValue, false);
235      this.resetNewAllNode(data, currentTreeList);
236    }
237  }
238
239  resetNewAllNode(data: MerageBean[], currentTreeList: any[]): void {
240    data.forEach((process) => {
241      process.children = [];
242    });
243    let values = currentTreeList.map((item: any) => {
244      item.children = [];
245      return item;
246    });
247    values.forEach((item: any) => {
248      if (item.parentNode != undefined) {
249        if (item.isStore == 0 && item.searchShow) {
250          let parentNode = item.parentNode;
251          while (parentNode != undefined && !(parentNode.isStore == 0 && parentNode.searchShow)) {
252            parentNode = parentNode.parentNode;
253          }
254          if (parentNode) {
255            item.currentTreeParentNode = parentNode;
256            parentNode.children.push(item);
257          }
258        }
259      }
260    });
261  }
262
263  findSearchNode(data: MerageBean[], search: string, parentSearch: boolean): void {
264    search = search.toLocaleLowerCase();
265    data.forEach((item) => {
266      if ((item.symbolName && item.symbolName.toLocaleLowerCase().includes(search)) || parentSearch) {
267        item.searchShow = true;
268        item.isSearch = item.symbolName != undefined && item.symbolName.toLocaleLowerCase().includes(search);
269        let parentNode = item.parent;
270        while (parentNode && !parentNode.searchShow) {
271          parentNode.searchShow = true;
272          parentNode = parentNode.parent;
273        }
274      } else {
275        item.searchShow = false;
276        item.isSearch = false;
277      }
278      if (item.children.length > 0) {
279        this.findSearchNode(item.children, search, item.searchShow);
280      }
281    });
282  }
283
284  clearSearchNode(currentTreeList: any[]): void {
285    currentTreeList.forEach((node) => {
286      node.searchShow = true;
287      node.isSearch = false;
288    });
289  }
290
291  splitAllProcess(allProcess: any[], splitMapData: any, list: any[]): void {
292    list.forEach((item: any) => {
293      allProcess.forEach((process) => {
294        if (item.select == '0') {
295          this.recursionChargeInitTree(splitMapData, process, item.name, item.type == 'symbol');
296        } else {
297          this.recursionPruneInitTree(splitMapData, process, item.name, item.type == 'symbol');
298        }
299      });
300      if (!item.checked) {
301        this.resotreAllNode(splitMapData, [item.name]);
302      }
303    });
304  }
305}
306
307export let merageBeanDataSplit = new MerageBeanDataSplit();
308
309export abstract class LogicHandler {
310  abstract handle(data: any): void;
311  queryData(eventId: string, queryName: string, sql: string, args: any): void {
312    self.postMessage({
313      id: eventId,
314      type: queryName,
315      isQuery: true,
316      args: args,
317      sql: sql,
318    });
319  }
320
321  abstract clearAll(): void;
322}
323
324let dec = new TextDecoder();
325
326export let setFileName = (path: string): string => {
327  let fileName = '';
328  if (path) {
329    let number = path.lastIndexOf('/');
330    if (number > 0) {
331      fileName = path.substring(number + 1);
332      return fileName;
333    }
334  }
335  return path;
336};
337
338let pagination = (page: number, pageSize: number, source: Array<any>): any[] => {
339  let offset = (page - 1) * pageSize;
340  return offset + pageSize >= source.length
341    ? source.slice(offset, source.length)
342    : source.slice(offset, offset + pageSize);
343};
344
345const PAGE_SIZE: number = 50_0000;
346export let postMessage = (id: any, action: string, results: Array<any>, pageSize: number = PAGE_SIZE): void => {
347  if (results.length > pageSize) {
348    let pageCount = Math.ceil(results.length / pageSize);
349    for (let i = 1; i <= pageCount; i++) {
350      let tag = 'start';
351      if (i == 1) {
352        tag = 'start';
353      } else if (i == pageCount) {
354        tag = 'end';
355      } else {
356        tag = 'sending';
357      }
358      let msg = new Msg();
359      msg.tag = tag;
360      msg.index = i;
361      msg.isSending = tag != 'end';
362      msg.data = pagination(i, PAGE_SIZE, results);
363      self.postMessage({
364        id: id,
365        action: action,
366        isSending: msg.tag != 'end',
367        results: msg,
368      });
369    }
370    results.length = 0;
371  } else {
372    let msg = new Msg();
373    msg.tag = 'end';
374    msg.index = 0;
375    msg.isSending = false;
376    msg.data = results;
377    self.postMessage({ id: id, action: action, results: msg });
378    results.length = 0;
379  }
380};
381export let translateJsonString = (str: string): string => {
382  return str //   .padding
383    .replace(/[\t|\r|\n]/g, '')
384    .replace(/\\/g, '\\\\');
385};
386
387export let convertJSON = (arrBuf: ArrayBuffer | Array<any>): any[] => {
388  if (arrBuf instanceof ArrayBuffer) {
389    let string = dec.decode(arrBuf);
390    let jsonArray = [];
391    string = string.substring(string.indexOf('\n') + 1);
392    if (!string) {
393    } else {
394      let parse;
395      let tansStr = translateJsonString(string);
396      try {
397        parse = JSON.parse(translateJsonString(string));
398      } catch {
399        tansStr = tansStr.replace(/[^\x20-\x7E]/g, '?'); //匹配乱码字符,将其转换为?
400        parse = JSON.parse(tansStr);
401      }
402      let columns = parse.columns;
403      let values = parse.values;
404      for (let i = 0; i < values.length; i++) {
405        let object: any = {};
406        for (let j = 0; j < columns.length; j++) {
407          object[columns[j]] = values[i][j];
408        }
409        jsonArray.push(object);
410      }
411    }
412    return jsonArray;
413  } else {
414    return arrBuf;
415  }
416};
417
418export let getByteWithUnit = (bytes: number): string => {
419  if (bytes < 0) {
420    return '-' + getByteWithUnit(Math.abs(bytes));
421  }
422  let currentBytes = bytes;
423  let kb1 = 1 << 10;
424  let mb = (1 << 10) << 10;
425  let gb = ((1 << 10) << 10) << 10; // 1 gb
426  let res = '';
427  if (currentBytes > gb) {
428    res += (currentBytes / gb).toFixed(2) + ' Gb';
429  } else if (currentBytes > mb) {
430    res += (currentBytes / mb).toFixed(2) + ' Mb';
431  } else if (currentBytes > kb1) {
432    res += (currentBytes / kb1).toFixed(2) + ' Kb';
433  } else {
434    res += Math.round(currentBytes) + ' byte';
435  }
436  return res;
437};
438
439export let getTimeString = (ns: number): string => {
440  let currentNs = ns;
441  let hour1 = 3600_000_000_000;
442  let minute1 = 60_000_000_000;
443  let second1 = 1_000_000_000;
444  let millisecond1 = 1_000_000;
445  let microsecond1 = 1_000;
446  let res = '';
447  if (currentNs >= hour1) {
448    res += Math.floor(currentNs / hour1) + 'h ';
449    currentNs = currentNs - Math.floor(currentNs / hour1) * hour1;
450  }
451  if (currentNs >= minute1) {
452    res += Math.floor(currentNs / minute1) + 'm ';
453    currentNs = currentNs - Math.floor(ns / minute1) * minute1;
454  }
455  if (currentNs >= second1) {
456    res += Math.floor(currentNs / second1) + 's ';
457    currentNs = currentNs - Math.floor(currentNs / second1) * second1;
458  }
459  if (currentNs >= millisecond1) {
460    res += Math.floor(currentNs / millisecond1) + 'ms ';
461    currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1;
462  }
463  if (currentNs >= microsecond1) {
464    res += Math.floor(currentNs / microsecond1) + 'μs ';
465    currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1;
466  }
467  if (currentNs > 0) {
468    res += currentNs + 'ns ';
469  }
470  if (res == '') {
471    res = ns + '';
472  }
473  return res;
474};
475
476export function getProbablyTime(ns: number): string {
477  let currentNs = ns;
478  let hour1 = 3600_000_000_000;
479  let minute1 = 60_000_000_000;
480  let second1 = 1_000_000_000;
481  let millisecond1 = 1_000_000;
482  let microsecond1 = 1_000;
483  let res = '';
484  if (currentNs >= hour1) {
485    res += (currentNs / hour1).toFixed(2) + 'h ';
486  } else if (currentNs >= minute1) {
487    res += (currentNs / minute1).toFixed(2) + 'm ';
488  } else if (currentNs >= second1) {
489    res += (currentNs / second1).toFixed(2) + 's ';
490  } else if (currentNs >= millisecond1) {
491    res += (currentNs / millisecond1).toFixed(2) + 'ms ';
492  } else if (currentNs >= microsecond1) {
493    res += (currentNs / microsecond1).toFixed(2) + 'μs ';
494  } else if (currentNs > 0) {
495    res += currentNs.toFixed(0) + 'ns ';
496  } else if (res == '') {
497    res = ns + '';
498  }
499  return res;
500}
501
502export function timeMsFormat2p(timeNs: number): string {
503  let currentNs = timeNs;
504  let oneHour = 3600_000;
505  let oneMinute1 = 60_000;
506  let oneSecond = 1_000; // 1 second
507  let commonResult = '';
508  if (currentNs >= oneHour) {
509    commonResult += Math.floor(currentNs / oneHour).toFixed(2) + 'h';
510    return commonResult;
511  }
512  if (currentNs >= oneMinute1) {
513    commonResult += Math.floor(currentNs / oneMinute1).toFixed(2) + 'min';
514    return commonResult;
515  }
516  if (currentNs >= oneSecond) {
517    commonResult += Math.floor(currentNs / oneSecond).toFixed(2) + 's';
518    return commonResult;
519  }
520  if (currentNs > 0) {
521    commonResult += currentNs.toFixed(2) + 'ms';
522    return commonResult;
523  }
524  if (commonResult === '') {
525    commonResult = '0s';
526  }
527  return commonResult;
528}
529
530export function formatRealDate(date: Date, fmt: string): string {
531  let obj = {
532    'M+': date.getMonth() + 1,
533    'd+': date.getDate(),
534    'h+': date.getHours(),
535    'm+': date.getMinutes(),
536    's+': date.getSeconds(),
537    'q+': Math.floor((date.getMonth() + 3) / 3),
538    S: date.getMilliseconds(),
539  };
540  if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
541  for (let key in obj) {
542    if (new RegExp('(' + key + ')').test(fmt)) {
543      // @ts-ignore
544      fmt = fmt.replace(
545        RegExp.$1,
546        // @ts-ignore
547        RegExp.$1.length == 1 ? obj[key] : ('00' + obj[key]).substr(('' + obj[key]).length)
548      );
549    }
550  }
551  return fmt;
552}
553
554export function formatRealDateMs(timeNs: number): string {
555  return formatRealDate(new Date(timeNs / 1000000), 'MM-dd hh:mm:ss.S');
556}
557
558export class JsProfilerSymbol {
559  id: number = 0;
560  nameId: number = 0;
561  name: string = '';
562  scriptId: number = 0;
563  urlId: number = 0;
564  url: string = '';
565  line: number = 0;
566  column: number = 0;
567  hitCount: number = 0;
568  childrenString?: string;
569  childrenIds: Array<number> = [];
570  children?: Array<JsProfilerSymbol>;
571  parentId: number = 0;
572  depth: number = -1;
573  cpuProfilerData?: JsProfilerSymbol;
574
575  public clone(): JsProfilerSymbol {
576    const cloneSymbol = new JsProfilerSymbol();
577    cloneSymbol.name = this.name;
578    cloneSymbol.url = this.url;
579    cloneSymbol.hitCount = this.hitCount;
580    cloneSymbol.children = new Array<JsProfilerSymbol>();
581    cloneSymbol.childrenIds = new Array<number>();
582    cloneSymbol.parentId = this.parentId;
583    cloneSymbol.depth = this.depth;
584    cloneSymbol.cpuProfilerData = this.cpuProfilerData;
585    return cloneSymbol;
586  }
587}
588
589export class HeapTreeDataBean {
590  MoudleName: string | undefined;
591  AllocationFunction: string | undefined;
592  symbolId: number = 0;
593  fileId: number = 0;
594  startTs: number = 0;
595  endTs: number = 0;
596  eventType: string | undefined;
597  depth: number = 0;
598  heapSize: number = 0;
599  eventId: number = 0;
600  addr: string = '';
601  callChinId: number = 0;
602}
603
604export class PerfCall {
605  sampleId: number = 0;
606  depth: number = 0;
607  name: string = '';
608}
609
610export class FileCallChain {
611  callChainId: number = 0;
612  depth: number = 0;
613  symbolsId: number = 0;
614  pathId: number = 0;
615  ip: string = '';
616  isThread: boolean = false;
617}
618
619export class DataCache {
620  public static instance: DataCache | undefined;
621  public dataDict = new Map<number, string>();
622  public eBpfCallChainsMap = new Map<number, Array<FileCallChain>>();
623  public nmFileDict = new Map<number, string>();
624  public nmHeapFrameMap = new Map<number, Array<HeapTreeDataBean>>();
625  public perfCountToMs = 1; // 1000 / freq
626  public perfCallChainMap: Map<number, PerfCall> = new Map<number, PerfCall>();
627  public jsCallChain: Array<JsProfilerSymbol> | undefined;
628  public jsSymbolMap = new Map<number, JsProfilerSymbol>();
629
630  public static getInstance(): DataCache {
631    if (!this.instance) {
632      this.instance = new DataCache();
633    }
634    return this.instance;
635  }
636
637  public clearAll(): void {
638    if (this.dataDict) {
639      this.dataDict.clear();
640    }
641    this.clearEBpf();
642    this.clearNM();
643    this.clearPerf();
644    this.clearJsCache();
645  }
646
647  public clearNM(): void {
648    this.nmFileDict.clear();
649    this.nmHeapFrameMap.clear();
650  }
651
652  public clearEBpf(): void {
653    this.eBpfCallChainsMap.clear();
654  }
655
656  public clearJsCache(): void {
657    if (this.jsCallChain) {
658      this.jsCallChain.length = 0;
659    }
660    this.jsSymbolMap.clear();
661  }
662
663  public clearPerf(): void {
664    this.perfCallChainMap.clear();
665  }
666}
667
668export class InitAnalysis {
669  public static instance: InitAnalysis | undefined;
670  public isInitAnalysis: boolean = true;
671  public static getInstance(): InitAnalysis {
672    if (!this.instance) {
673      this.instance = new InitAnalysis();
674    }
675    return this.instance;
676  }
677}
678