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