• 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 */
15import { JsCpuProfilerChartFrame, JsCpuProfilerTabStruct } from '../../bean/JsStruct';
16import { DataCache, type JsProfilerSymbol, LogicHandler, convertJSON } from './ProcedureLogicWorkerCommon';
17
18const ROOT_ID = 1;
19const LAMBDA_FUNCTION_NAME = '(anonymous)';
20export class ProcedureLogicWorkerJsCpuProfiler extends LogicHandler {
21  private currentEventId!: string;
22  private dataCache = DataCache.getInstance();
23  private samples: Array<JsCpuProfilerSample> = []; // Array index equals id;
24  private tabDataId = 0;
25  private chartData: Array<JsCpuProfilerChartFrame> = [];
26  private leftNs: number = 0;
27  private rightNs: number = 0;
28  private type: string = '';
29  private params: unknown;
30  private action: string = '';
31
32  public handle(msg: unknown): void {
33    if (!msg) {
34      return;
35    }
36    //@ts-ignore
37    this.currentEventId = msg.id;
38    //@ts-ignore
39    this.type = msg.type;
40    //@ts-ignore
41    this.action = msg.action;
42    //@ts-ignore
43    this.params = msg.params;
44    if (this.type) {
45      switch (this.type) {
46        case 'jsCpuProfiler-call-chain':
47          this.jsCpuProfilerCallChain();
48          break;
49        case 'jsCpuProfiler-call-tree':
50          this.jsCpuProfilerCallTree();
51          break;
52        case 'jsCpuProfiler-bottom-up':
53          this.jsCpuProfilerBottomUp();
54          break;
55        case 'jsCpuProfiler-statistics':
56          this.jsCpuProfilerStatistics();
57          break;
58      }
59    }
60  }
61  private jsCpuProfilerCallChain(): void {
62    if (!this.dataCache.jsCallChain || this.dataCache.jsCallChain.length === 0) {
63      //@ts-ignore
64      this.dataCache.jsCallChain = convertJSON(this.params.list) || [];
65      this.createCallChain();
66    }
67  }
68  private jsCpuProfilerCallTree(): void {
69    this.tabDataId = 0;
70    self.postMessage({
71      id: this.currentEventId,
72      action: this.action,
73      //@ts-ignore
74      results: this.combineTopDownData(this.params, null),
75    });
76  }
77  private jsCpuProfilerBottomUp(): void {
78    this.tabDataId = 0;
79    self.postMessage({
80      id: this.currentEventId,
81      action: this.action,
82      //@ts-ignore
83      results: this.combineBottomUpData(this.params),
84    });
85  }
86  private jsCpuProfilerStatistics(): void {
87    if (!this.dataCache.jsCallChain || this.dataCache.jsCallChain.length === 0) {
88      this.initCallChain();
89    }
90    //@ts-ignore
91    if (this.params.data) {
92      //@ts-ignore
93      this.chartData = this.params.data;
94      //@ts-ignore
95      this.leftNs = this.params.leftNs;
96      //@ts-ignore
97      this.rightNs = this.params.rightNs;
98    }
99    //@ts-ignore
100    if (this.params.list) {
101      //@ts-ignore
102      this.samples = convertJSON(this.params.list) || [];
103      this.setChartDataType();
104      self.postMessage({
105        id: this.currentEventId,
106        action: this.action,
107        results: this.calStatistic(this.chartData, this.leftNs, this.rightNs),
108      });
109    } else {
110      this.queryChartData();
111    }
112  }
113  public clearAll(): void {
114    this.dataCache.clearAll();
115    this.samples.length = 0;
116    this.chartData.length = 0;
117  }
118
119  private calStatistic(
120    chartData: Array<JsCpuProfilerChartFrame>,
121    leftNs: number | undefined,
122    rightNs: number | undefined
123  ): Map<SampleType, number> {
124    const typeMap = new Map<SampleType, number>();
125    const samplesIdsArr: Array<unknown> = [];
126    const samplesIds = this.findSamplesIds(chartData, [], []);
127    for (const id of samplesIds) {
128      const sample = this.samples[id];
129      if (!sample || sample.type === undefined) {
130        continue;
131      }
132      let sampleTotalTime = sample.dur;
133      if (leftNs && rightNs) {
134        // 不在框选范围内的不做处理
135        if (sample.startTime > rightNs || sample.endTime < leftNs) {
136          continue;
137        }
138        // 在框选范围内的被只框选到一部分的根据框选范围调整时间
139        const startTime = sample.startTime < leftNs ? leftNs : sample.startTime;
140        const endTime = sample.endTime > rightNs ? rightNs : sample.endTime;
141        sampleTotalTime = endTime - startTime;
142      }
143
144      if (!samplesIdsArr.includes(sample)) {
145        samplesIdsArr.push(sample);
146        let typeDur = typeMap.get(sample.type);
147        if (typeDur) {
148          typeMap.set(sample.type, typeDur + sampleTotalTime);
149        } else {
150          typeMap.set(sample.type, sampleTotalTime);
151        }
152      }
153    }
154    return typeMap;
155  }
156
157  private findSamplesIds(
158    chartData: Array<JsCpuProfilerChartFrame>,
159    lastLayerData: Array<JsCpuProfilerChartFrame>,
160    samplesIds: Array<number>
161  ): number[] {
162    for (const data of chartData) {
163      if (data.isSelect && data.selfTime > 0 && !lastLayerData.includes(data)) {
164        lastLayerData.push(data);
165        samplesIds.push(...data.samplesIds);
166      } else if (data.children.length > 0) {
167        this.findSamplesIds(data.children, lastLayerData, samplesIds);
168      }
169    }
170    return samplesIds;
171  }
172
173  private setChartDataType(): void {
174    for (let sample of this.samples) {
175      const chartData = this.dataCache.jsSymbolMap.get(sample.functionId);
176      if (chartData?.id === ROOT_ID) {
177        sample.type = SampleType.OTHER;
178        continue;
179      }
180      if (chartData) {
181        let type: string;
182        chartData.name = this.dataCache.dataDict.get(chartData.nameId) || LAMBDA_FUNCTION_NAME;
183        if (chartData.name) {
184          type = chartData.name.substring(chartData.name!.lastIndexOf('(') + 1, chartData.name!.lastIndexOf(')'));
185          switch (type) {
186            case 'NAPI':
187              sample.type = SampleType.NAPI;
188              break;
189            case 'ARKUI_ENGINE':
190              sample.type = SampleType.ARKUI_ENGINE;
191              break;
192            case 'BUILTIN':
193              sample.type = SampleType.BUILTIN;
194              break;
195            case 'GC':
196              sample.type = SampleType.GC;
197              break;
198            case 'AINT':
199              sample.type = SampleType.AINT;
200              break;
201            case 'CINT':
202              sample.type = SampleType.CINT;
203              break;
204            case 'AOT':
205              sample.type = SampleType.AOT;
206              break;
207            case 'RUNTIME':
208              sample.type = SampleType.RUNTIME;
209              break;
210            default:
211              if (chartData.name !== '(program)') {
212                sample.type = SampleType.OTHER;
213              }
214              break;
215          }
216        }
217      }
218    }
219  }
220
221  /**
222   * 建立callChain每个函数的联系,设置depth跟children
223   */
224  private createCallChain(): void {
225    const jsSymbolMap = this.dataCache.jsSymbolMap;
226    for (const item of this.dataCache.jsCallChain!) {
227      jsSymbolMap.set(item.id, item);
228    }
229  }
230
231  /**
232   * 同级使用广度优先算法,非同级使用深度优先算法,遍历泳道图树结构所有数据,
233   * 将name,url,depth,parent相同的函数合并,构建成一个top down的树结构
234   * @param combineSample 泳道图第一层数据,非第一层为null
235   * @param parent 泳道图合并过的函数,第一层为null
236   * @returns 返回第一层树结构(第一层数据通过children囊括了所有的函数)
237   */
238  private combineTopDownData(
239    combineSample: Array<JsCpuProfilerChartFrame> | null,
240    parent: JsCpuProfilerTabStruct | null
241  ): Array<JsCpuProfilerTabStruct> {
242    const sameSymbolMap = new Map<string, JsCpuProfilerTabStruct>();
243    const currentLevelData: Array<JsCpuProfilerTabStruct> = [];
244    const chartArray = combineSample || parent?.chartFrameChildren;
245    if (!chartArray) {
246      return [];
247    }
248    // 同级广度优先 便于数据合并
249    for (const chartFrame of chartArray) {
250      if (!chartFrame.isSelect) {
251        continue;
252      }
253      // 该递归函数已经保证depth跟parent相同,固只需要判断name跟url相同即可
254      let symbolKey = chartFrame.nameId + ' ' + chartFrame.urlId;
255      //   // lambda 表达式需要根据行列号区分是不是同一个函数
256      if (chartFrame.nameId === 0) {
257        symbolKey += ' ' + chartFrame.line + ' ' + chartFrame.column;
258      }
259      let tabCallFrame: JsCpuProfilerTabStruct;
260      if (sameSymbolMap.has(symbolKey)) {
261        tabCallFrame = sameSymbolMap.get(symbolKey)!;
262        tabCallFrame.totalTime += chartFrame.totalTime;
263        tabCallFrame.selfTime += chartFrame.selfTime;
264      } else {
265        tabCallFrame = this.chartFrameToTabStruct(chartFrame);
266        sameSymbolMap.set(symbolKey, tabCallFrame);
267        currentLevelData.push(tabCallFrame);
268        if (parent) {
269          parent.children.push(tabCallFrame);
270        }
271      }
272      tabCallFrame.chartFrameChildren?.push(...chartFrame.children);
273    }
274    // 非同级深度优先,便于设置children,同时保证下一级函数depth跟parent都相同
275    for (const data of currentLevelData) {
276      this.combineTopDownData(null, data);
277      data.chartFrameChildren = [];
278    }
279    if (combineSample) {
280      // 第一层为返回给Tab页的数据
281      return currentLevelData;
282    } else {
283      return [];
284    }
285  }
286
287  /**
288   * copy整体调用链,从栈顶函数一直copy到栈底函数,
289   * 给Parent设置selfTime,totalTime设置为children的selfTime,totalTime
290   *  */
291  private copyParent(frame: JsCpuProfilerChartFrame, chartFrame: JsCpuProfilerChartFrame): void {
292    frame.children = [];
293    if (chartFrame.parent) {
294      const copyParent = this.cloneChartFrame(chartFrame.parent);
295      copyParent.selfTime = frame.selfTime;
296      copyParent.totalTime = frame.totalTime;
297      frame.children.push(copyParent);
298      this.copyParent(copyParent, chartFrame.parent);
299    }
300  }
301
302  /**
303   * 步骤1:框选/点选的chart树逆序
304   * 步骤2:将name,url,parent,层级相同的函数合并
305   * @param chartTreeArray ui传递的树结构
306   * @returns 合并的Array<JsCpuProfilerChartFrame>树结构
307   */
308  private combineBottomUpData(chartTreeArray: Array<JsCpuProfilerChartFrame>): Array<JsCpuProfilerTabStruct> {
309    const reverseTreeArray: Array<JsCpuProfilerChartFrame> = [];
310    // 将树结构逆序,parent变成children
311    this.reverseChartFrameTree(chartTreeArray, reverseTreeArray);
312    // 将逆序的树结构合并返回
313    return this.combineTopDownData(reverseTreeArray, null);
314  }
315
316  /**
317   * 树结构逆序
318   * @param chartTreeArray 正序的树结构
319   * @param reverseTreeArray 逆序的树结构
320   */
321  private reverseChartFrameTree(
322    chartTreeArray: Array<JsCpuProfilerChartFrame>,
323    reverseTreeArray: Array<JsCpuProfilerChartFrame>
324  ): void {
325    const recursionTree = (chartFrame: JsCpuProfilerChartFrame): void => {
326      // isSelect为框选/点选范围内的函数,其他都不需要处理
327      if (!chartFrame.isSelect) {
328        return;
329      }
330      //界面第一层只显示栈顶函数,只有栈顶函数的selfTime > 0
331      if (chartFrame.selfTime > 0) {
332        const copyFrame = this.cloneChartFrame(chartFrame);
333        // 每个栈顶函数的parent的时间为栈顶函数的时间
334        copyFrame.selfTime = chartFrame.selfTime;
335        copyFrame.totalTime = chartFrame.totalTime;
336        reverseTreeArray.push(copyFrame);
337        // 递归处理parent的的totalTime selfTime
338        this.copyParent(copyFrame, chartFrame);
339      }
340
341      if (chartFrame.children.length > 0) {
342        for (const children of chartFrame.children) {
343          children.parent = chartFrame;
344          recursionTree(children);
345        }
346      }
347    };
348
349    //递归树结构
350    for (const chartFrame of chartTreeArray) {
351      recursionTree(chartFrame);
352    }
353  }
354  /**
355   * 将泳道图数据JsCpuProfilerChartFrame转化为JsCpuProfilerTabStruct 作为绘制Ta页的结构
356   * @param chartCallChain 泳道图函数信息
357   * @returns JsCpuProfilerTabStruct
358   */
359  private chartFrameToTabStruct(chartCallChain: JsCpuProfilerChartFrame): JsCpuProfilerTabStruct {
360    const tabData = new JsCpuProfilerTabStruct(
361      chartCallChain.nameId,
362      chartCallChain.selfTime,
363      chartCallChain.totalTime,
364      chartCallChain.depth,
365      chartCallChain.urlId,
366      chartCallChain.line,
367      chartCallChain.column,
368      chartCallChain.scriptName,
369      this.tabDataId++
370    );
371    return tabData;
372  }
373
374  private cloneChartFrame(frame: JsCpuProfilerChartFrame): JsCpuProfilerChartFrame {
375    const copyFrame = new JsCpuProfilerChartFrame(
376      frame.id,
377      frame.nameId,
378      frame.startTime,
379      frame.endTime,
380      frame.totalTime,
381      frame.depth,
382      frame.urlId,
383      frame.line,
384      frame.column
385    );
386    copyFrame.parentId = frame.parentId;
387    copyFrame.isSelect = true;
388    copyFrame.scriptName = frame.scriptName;
389    return copyFrame;
390  }
391
392  private initCallChain(): void {
393    const sql = `SELECT function_id AS id,
394                        function_index AS nameId,
395                        script_id AS scriptId,
396                        url_index AS urlId,
397                        line_number as line,
398                        column_number as column,
399                        hit_count AS hitCount,
400                        children AS childrenString,
401                        parent_id AS parentId
402                    FROM
403                        js_cpu_profiler_node`;
404    this.queryData(this.currentEventId!, 'jsCpuProfiler-call-chain', sql, {});
405  }
406
407  private queryChartData(): void {
408    const sql = `SELECT id,
409                    function_id AS functionId,
410                    start_time - start_ts AS startTime,
411                    end_time - start_ts AS endTime,
412                    dur
413                    FROM js_cpu_profiler_sample,trace_range`;
414    this.queryData(this.currentEventId!, 'jsCpuProfiler-statistics', sql, {});
415  }
416}
417
418export class JsCpuProfilerSample {
419  id: number = 0;
420  functionId: number = 0;
421  functionIndex: number = 0;
422  startTime: number = 0;
423  endTime: number = 0;
424  dur: number = 0;
425  type: SampleType = SampleType.OTHER;
426  stack?: Array<JsProfilerSymbol>;
427  cpuProfilerData?: JsCpuProfilerSample;
428}
429
430export enum SampleType {
431  OTHER = 'OTHER',
432  NAPI = 'NAPI',
433  ARKUI_ENGINE = 'ARKUI_ENGINE',
434  BUILTIN = 'BUILTIN',
435  GC = 'GC',
436  AINT = 'AINT',
437  CINT = 'CINT',
438  AOT = 'AOT',
439  RUNTIME = 'RUNTIME',
440}
441