• 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 { DataCache, JsProfilerSymbol, convertJSON } from '../../database/logic-worker/ProcedureLogicWorkerCommon';
17import { JsCpuProfilerChartFrame, type JsCpuProfilerUIStruct } from '../../bean/JsStruct';
18import { JsCpuProfilerSample, SampleType } from '../logic-worker/ProcedureLogicWorkerJsCpuProfiler';
19import { TraficEnum } from './utils/QueryEnum';
20import { Args } from './CommonArgs';
21
22const dataCache = DataCache.getInstance();
23const ROOT_ID = 1;
24let samples = Array<JsCpuProfilerSample>(); // Array index equals id;
25const jsCallChain: Array<JsCpuProfilerChartFrame> = [];
26let chartId: number = 0;
27const jsDataCache: {
28  childrenIds: Array<Array<number>>;
29  column: Array<number>;
30  depth: Array<number>;
31  endTime: Array<number>;
32  id: Array<number>;
33  line: Array<number>;
34  nameId: Array<number>;
35  parentId: Array<number>;
36  samplesIds: Array<Array<number>>;
37  selfTime: Array<number>;
38  startTime: Array<number>;
39  totalTime: Array<number>;
40  urlId: Array<number>;
41  maxDepth: number;
42} = {
43  samplesIds: [],
44  childrenIds: [],
45  column: [],
46  depth: [],
47  endTime: [],
48  id: [],
49  line: [],
50  nameId: [],
51  parentId: [],
52  selfTime: [],
53  startTime: [],
54  totalTime: [],
55  urlId: [],
56  maxDepth: 1,
57};
58
59export const initCallChainDataSql = (args: unknown): string => {
60  const sql = `SELECT function_id AS id,
61              0 As functionId,
62            0 AS startTime,
63            0 As endTime,
64            0 As dur,
65            function_index AS nameId,
66            url_index AS urlId,
67            line_number as line,
68            column_number as column,
69            hit_count AS hitCount,
70            children AS childrenString,
71            parent_id AS parentId
72            FROM js_cpu_profiler_node`;
73  return sql;
74};
75
76export const queryChartDataSqlMem = (args: Args): string => {
77  const sql = `SELECT id,
78            function_id AS functionId,
79            start_time - ${args.recordStartNS} AS startTime,
80            end_time - ${args.recordStartNS} AS endTime,
81            dur
82            FROM js_cpu_profiler_sample`;
83  return sql;
84};
85
86export function cpuProfilerDataReceiver(data: unknown, proc: Function): void {
87  //@ts-ignore
88  let sql = initCallChainDataSql(data.params);
89  let res = proc(sql);
90  if (res.length > 0) {
91    if (!dataCache.jsCallChain || dataCache.jsCallChain.length === 0) {
92      dataCache.jsCallChain = res;
93      //@ts-ignore
94      createCallChain(data.params.trafic);
95    }
96    //@ts-ignore
97    let sql = queryChartDataSqlMem(data.params);
98    let chartData = proc(sql);
99    if (chartData.length > 0) {
100      //@ts-ignore
101      samples = convertJSON(chartData);
102      arrayBufferHandler(data, samples, true);
103    }
104  }
105}
106
107/**
108 * 建立callChain每个函数的联系,设置depth跟children
109 */
110function createCallChain(trafic: TraficEnum): void {
111  const jsSymbolMap = dataCache.jsSymbolMap;
112  const symbol = new JsProfilerSymbol();
113  for (let data of dataCache.jsCallChain!) {
114    let sample = (trafic !== TraficEnum.Memory ? data.cpuProfilerData : data) || symbol;
115    let item = data.cpuProfilerData || data;
116    if (!item.childrenString) {
117      item.childrenString = '';
118    }
119    jsSymbolMap.set(item.id, item);
120    //root不需要显示,depth为-1
121    if (item.id === ROOT_ID) {
122      item.depth = -1;
123    }
124    if (item.parentId > 0) {
125      let parentSymbol = jsSymbolMap.get(item.parentId);
126      if (parentSymbol) {
127        if (!parentSymbol.children) {
128          parentSymbol.children = new Array<JsProfilerSymbol>();
129          parentSymbol.childrenIds = new Array<number>();
130          parentSymbol.childrenString = '';
131        }
132        parentSymbol.children.push(item);
133        parentSymbol.childrenString! += `${item.id},`;
134        parentSymbol.childrenIds.push(item.id);
135        item.depth = parentSymbol.depth + 1;
136      }
137    }
138  }
139}
140
141function combineChartData(res: Array<JsCpuProfilerSample>, trafic: TraficEnum): Array<JsCpuProfilerChartFrame> {
142  const combineSample = new Array<JsCpuProfilerChartFrame>();
143  const symbol = new JsCpuProfilerSample();
144  for (let data of res) {
145    let sample = (trafic !== TraficEnum.Memory ? data.cpuProfilerData : data) || symbol;
146    const stackTopSymbol = dataCache.jsSymbolMap.get(sample.functionId);
147    // root 节点不需要显示
148    if (stackTopSymbol?.id === ROOT_ID) {
149      sample.type = SampleType.OTHER;
150      continue;
151    }
152    if (stackTopSymbol) {
153      // 获取栈顶函数的整条调用栈为一个数组 下标0为触发的栈底函数
154      sample.stack = getFullCallChainOfNode(stackTopSymbol);
155      if (combineSample.length === 0) {
156        // 首次combineSample没有数据时,用第一条数据创建一个调用树
157        createNewChartFrame(sample, combineSample);
158      } else {
159        const lastCallChart = combineSample[combineSample.length - 1];
160        if (isSymbolEqual(sample.stack[0], lastCallChart) && lastCallChart.endTime === sample.startTime) {
161          combineCallChain(lastCallChart, sample);
162        } else {
163          // 一个调用链栈底函数与前一个不同时,需要新加入到combineSample
164          createNewChartFrame(sample, combineSample);
165        }
166      }
167    }
168  }
169  return combineSample;
170}
171
172/**
173 * 根据每个sample的栈顶函数,获取完整的调用栈
174 * @param node 栈顶函数
175 * @returns 完整的调用栈
176 */
177function getFullCallChainOfNode(node: JsProfilerSymbol): Array<JsProfilerSymbol> {
178  const callChain = new Array<JsProfilerSymbol>();
179  callChain.push(node);
180  while (node.parentId !== 0) {
181    const parent = dataCache.jsSymbolMap.get(node.parentId);
182    // id 1 is root Node
183    if (!parent || parent.id <= ROOT_ID) {
184      break;
185    }
186    callChain.push(parent);
187    node = parent;
188  }
189  callChain.reverse();
190  return callChain;
191}
192
193function createNewChartFrame(sample: JsCpuProfilerSample, combineSample: Array<JsCpuProfilerChartFrame>): void {
194  let lastSymbol: JsCpuProfilerChartFrame;
195  for (const [idx, symbol] of sample.stack!.entries()) {
196    if (idx === 0) {
197      lastSymbol = symbolToChartFrame(sample, symbol);
198      combineSample.push(lastSymbol);
199    } else {
200      const callFrame = symbolToChartFrame(sample, symbol);
201      lastSymbol!.children.push(callFrame);
202      lastSymbol!.childrenIds.push(callFrame.id);
203      callFrame.parentId = lastSymbol!.id;
204      lastSymbol = callFrame;
205    }
206    if (idx + 1 === sample.stack?.length) {
207      lastSymbol.selfTime = sample.dur;
208    }
209  }
210}
211
212/**
213 * 创建一个JsCpuProfilerChartFrame 作为绘制泳道图的结构
214 * @param sample 数据库样本数据
215 * @param symbol 样本的每一个函数
216 * @returns JsCpuProfilerChartFrame
217 */
218function symbolToChartFrame(sample: JsCpuProfilerSample, symbol: JsProfilerSymbol): JsCpuProfilerChartFrame {
219  const chartFrame = new JsCpuProfilerChartFrame(
220    chartId++,
221    symbol.nameId,
222    sample.startTime,
223    sample.endTime,
224    sample.dur,
225    symbol.depth,
226    symbol.urlId,
227    symbol.line,
228    symbol.column
229  );
230  chartFrame.samplesIds.push(sample.id);
231  return chartFrame;
232}
233
234function isSymbolEqual(symbol: JsProfilerSymbol, uiData: JsCpuProfilerUIStruct): boolean {
235  return symbol.nameId === uiData.nameId && symbol.urlId === uiData.urlId;
236}
237
238/**
239 * 相邻的两个sample的name,url,depth相同,且上一个的endTime等于下一个的startTime,
240 * 则两个sample的调用栈合并
241 * @param lastCallTree 上一个已经合并的树结构调用栈
242 * @param sample 当前样本数据
243 */
244function combineCallChain(lastCallTree: JsCpuProfilerChartFrame, sample: JsCpuProfilerSample): void {
245  let lastCallTreeSymbol = lastCallTree;
246  let parentCallFrame: JsCpuProfilerChartFrame;
247  let isEqual = true;
248  for (const [idx, symbol] of sample.stack!.entries()) {
249    // 是否为每次采样的栈顶函数
250    const isLastSymbol = idx + 1 === sample.stack?.length;
251    if (
252      isEqual &&
253      isSymbolEqual(symbol, lastCallTreeSymbol) &&
254      lastCallTreeSymbol.depth === idx &&
255      lastCallTreeSymbol.endTime === sample.startTime
256    ) {
257      // 如果函数名跟depth匹配,则更新函数的持续时间
258      lastCallTreeSymbol.endTime = sample.endTime;
259      lastCallTreeSymbol.totalTime = sample.endTime - lastCallTreeSymbol.startTime;
260      lastCallTreeSymbol.samplesIds.push(sample.id);
261      let lastChildren = lastCallTreeSymbol.children;
262      parentCallFrame = lastCallTreeSymbol;
263      if (lastChildren && lastChildren.length > 0) {
264        lastCallTreeSymbol = lastChildren[lastChildren.length - 1];
265      }
266      isEqual = true;
267    } else {
268      // 如果不匹配,则作为新的分支添加到lastCallTree
269      const deltaFrame = symbolToChartFrame(sample, symbol);
270      parentCallFrame!.children.push(deltaFrame);
271      parentCallFrame!.childrenIds.push(deltaFrame.id);
272      deltaFrame.parentId = parentCallFrame!.id;
273      parentCallFrame = deltaFrame;
274      isEqual = false;
275    }
276    // 每次采样的栈顶函数的selfTime为该次采样数据的时间
277    if (isLastSymbol) {
278      parentCallFrame.selfTime += sample.dur;
279    }
280  }
281}
282
283function arrayBufferHandler(data: unknown, res: JsCpuProfilerSample[], transfer: boolean): void {
284  //@ts-ignore
285  let result = combineChartData(res, data.params.trafic);
286  clearJsCacheData();
287  const getArrayData = (combineData: Array<JsCpuProfilerChartFrame>): void => {
288    for (let item of combineData) {
289      if (item.depth > -1) {
290        jsDataCache.id.push(item.id);
291        jsDataCache.startTime.push(item.startTime);
292        jsDataCache.endTime.push(item.endTime);
293        jsDataCache.selfTime.push(item.selfTime);
294        jsDataCache.totalTime.push(item.totalTime);
295        jsDataCache.column.push(item.column);
296        jsDataCache.line.push(item.line);
297        jsDataCache.depth.push(item.depth);
298        jsDataCache.parentId.push(item.parentId);
299        jsDataCache.nameId.push(item.nameId);
300        jsDataCache.samplesIds.push([...item.samplesIds]);
301        jsDataCache.urlId.push(item.urlId);
302        jsDataCache.childrenIds.push([...item.childrenIds]);
303        if (item.depth + 1 > jsDataCache.maxDepth) {
304          jsDataCache.maxDepth = item.depth + 1;
305        }
306        if (item.children && item.children.length > 0) {
307          getArrayData(item.children);
308        }
309      }
310    }
311  };
312  getArrayData(result);
313  setTimeout((): void => {
314    arrayBufferCallback(data, transfer);
315  }, 150);
316}
317function arrayBufferCallback(data: unknown, transfer: boolean): void {
318  let dataFilter = jsDataCache;
319  let len = dataFilter!.startTime!.length;
320  const arkTs = new ArkTS(len);
321  for (let i = 0; i < len; i++) {
322    arkTs.column[i] = dataFilter.column[i];
323    arkTs.depth[i] = dataFilter.depth[i];
324    arkTs.endTime[i] = dataFilter.endTime[i];
325    arkTs.id[i] = dataFilter.id[i];
326    arkTs.line[i] = dataFilter.line[i];
327    arkTs.nameId[i] = dataFilter.nameId[i];
328    arkTs.parentId[i] = dataFilter.parentId[i];
329    arkTs.samplesIds[i] = [...dataFilter.samplesIds[i]];
330    arkTs.selfTime[i] = dataFilter.selfTime[i];
331    arkTs.startTime[i] = dataFilter.startTime[i];
332    arkTs.totalTime[i] = dataFilter.totalTime[i];
333    arkTs.urlId[i] = dataFilter.urlId[i];
334    arkTs.childrenIds[i] = [...dataFilter.childrenIds[i]];
335  }
336  postMessage(data, transfer, arkTs, len);
337  // 合并完泳道图数据之后,Tab页不再需要缓存数据
338  if (jsCallChain) {
339    dataCache.jsCallChain!.length = 0;
340  }
341  dataCache.jsSymbolMap!.clear();
342}
343function postMessage(data: unknown, transfer: boolean, arkTs: ArkTS, len: number): void {
344  (self as unknown as Worker).postMessage(
345    {
346      //@ts-ignore
347      id: data.id,
348      //@ts-ignore
349      action: data.action,
350      results: transfer
351        ? {
352            column: arkTs.column.buffer,
353            depth: arkTs.depth.buffer,
354            endTime: arkTs.endTime.buffer,
355            id: arkTs.id.buffer,
356            line: arkTs.line.buffer,
357            nameId: arkTs.nameId.buffer,
358            parentId: arkTs.parentId.buffer,
359            samplesIds: arkTs.samplesIds,
360            selfTime: arkTs.selfTime.buffer,
361            startTime: arkTs.startTime.buffer,
362            totalTime: arkTs.totalTime.buffer,
363            urlId: arkTs.urlId.buffer,
364            childrenIds: arkTs.childrenIds,
365            maxDepth: jsDataCache.maxDepth,
366          }
367        : {},
368      len: len,
369    },
370    transfer
371      ? [
372          arkTs.column.buffer,
373          arkTs.depth.buffer,
374          arkTs.endTime.buffer,
375          arkTs.id.buffer,
376          arkTs.line.buffer,
377          arkTs.parentId.buffer,
378          arkTs.selfTime.buffer,
379          arkTs.startTime.buffer,
380          arkTs.totalTime.buffer,
381        ]
382      : []
383  );
384}
385
386function ns2x(ns: number, startNS: number, endNS: number, duration: number, width: number): number {
387  if (endNS === 0) {
388    endNS = duration;
389  }
390  let xSize: number = ((ns - startNS) * width) / (endNS - startNS);
391  xSize = xSize < 0 ? 0 : xSize > width ? width : xSize;
392  return xSize;
393}
394
395function clearJsCacheData(): void {
396  chartId = 0;
397  jsDataCache.childrenIds = [];
398  jsDataCache.column = [];
399  jsDataCache.depth = [];
400  jsDataCache.endTime = [];
401  jsDataCache.id = [];
402  jsDataCache.line = [];
403  jsDataCache.nameId = [];
404  jsDataCache.parentId = [];
405  jsDataCache.samplesIds = [];
406  jsDataCache.selfTime = [];
407  jsDataCache.startTime = [];
408  jsDataCache.totalTime = [];
409  jsDataCache.urlId = [];
410  jsDataCache.maxDepth = 1;
411}
412
413class ArkTS {
414  column: Int32Array;
415  depth: Int32Array;
416  endTime: Float64Array;
417  id: Int32Array;
418  line: Int32Array;
419  nameId: Int32Array;
420  parentId: Int32Array;
421  samplesIds: Array<unknown>;
422  selfTime: Float64Array;
423  startTime: Float64Array;
424  totalTime: Float64Array;
425  urlId: Int32Array;
426  childrenIds: Array<unknown>;
427
428  constructor(len: number) {
429    this.column = new Int32Array(len);
430    this.depth = new Int32Array(len);
431    this.endTime = new Float64Array(len);
432    this.id = new Int32Array(len);
433    this.line = new Int32Array(len);
434    this.nameId = new Int32Array(len);
435    this.parentId = new Int32Array(len);
436    this.samplesIds = [];
437    this.selfTime = new Float64Array(len);
438    this.startTime = new Float64Array(len);
439    this.totalTime = new Float64Array(len);
440    this.urlId = new Int32Array(len);
441    this.childrenIds = [];
442  }
443}
444