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