• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2021 Huawei Device Co., Ltd.
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14import { ConstructorComparison } from '../../../../js-heap/model/UiStruct';
15import { TraficEnum } from '../utils/QueryEnum';
16
17interface HiPerfSampleType {
18  callchainId: number;
19  startTs: number;
20  eventCount: number;
21  threadId: number;
22  cpuId: number;
23  eventTypeId: number;
24}
25
26const dataCache: {
27  startTs: Array<number>;
28  dur: Array<number>;
29  depth: Array<number>;
30  eventCount: Array<number>;
31  symbolId: Array<number>;
32  fileId: Array<number>;
33  callchainId: Array<number>;
34  selfDur: Array<number>;
35  name: Array<number>;
36  callstack: Map<string, any>;
37  sampleList: Array<HiPerfSampleType>;
38  maxDepth: number;
39} = {
40  callstack: new Map<string, any>(),
41  sampleList: [],
42  maxDepth: 1,
43  startTs: [],
44  dur: [],
45  depth: [],
46  eventCount: [],
47  symbolId: [],
48  fileId: [],
49  callchainId: [],
50  selfDur: [],
51  name: [],
52};
53
54export const chartHiperfCallChartDataSql = (args: any): string => {
55  const sql = `
56    select callchain_id                             as callchainId,
57           timestamp_trace - ${args.recordStartNS}  as startTs,
58           event_count                              as eventCount,
59           A.thread_id                              as threadId,
60           cpu_id                                   as cpuId,
61           event_type_id                            as eventTypeId
62    from perf_sample A
63    where callchain_id != -1 and A.thread_id != 0
64    order by cpuId, startTs`;
65  return sql;
66};
67
68export function hiPerfCallChartDataHandler(data: any, proc: Function): void {
69  if (data.params.isCache) {
70    let res: Array<any> = proc(chartHiperfCallChartDataSql(data.params));
71    for (let i = 0; i < res.length; i++) {
72      if (i > 0) {
73        if (res[i].cpuId === res[i - 1].cpuId) {
74          res[i - 1].dur = res[i].startTs - res[i - 1].startTs;
75        } else {
76          res[i - 1].dur = data.params.endNS - res[i - 1].startTs;
77        }
78      }
79      if (i === res.length - 1) {
80        res[i].dur = data.params.endNS - res[i].startTs;
81      }
82    }
83    dataCache.sampleList = res;
84    (self as unknown as Worker).postMessage(
85      {
86        len: 0,
87        id: data.id,
88        action: data.action,
89        results: 'ok',
90      },
91      []
92    );
93  } else {
94    let res: Array<any> = [];
95    if (!data.params.isComplete) {
96      res = dataCache.sampleList.filter((it) => {
97        let cpuThreadFilter = data.params.type === 0 ? it.cpuId === data.params.id : it.threadId === data.params.id;
98        let eventTypeFilter = data.params.eventTypeId === -2 ? true : it.eventTypeId === data.params.eventTypeId;
99        return cpuThreadFilter && eventTypeFilter;
100      });
101    }
102    arrayBufferHandler(data, res, true, !data.params.isComplete);
103  }
104}
105
106export function hiPerfCallStackCacheHandler(data: any, proc: Function): void {
107  if (data.params.isCache) {
108    hiPerfCallChartClearCache(true);
109    arrayBufferCallStackHandler(data, proc(hiPerfCallStackDataCacheSql()));
110  }
111}
112
113function arrayBufferHandler(data: any, res: any[], transfer: boolean, loadData: boolean): void {
114  if (loadData) {
115    let result = combinePerfSampleByCallChainId(res, data.params);
116    hiPerfCallChartClearCache(false);
117    const getArrayData = (combineData: Array<any>): void => {
118      for (let item of combineData) {
119        if (item.depth > -1) {
120          dataCache.startTs.push(item.startTime);
121          dataCache.dur.push(item.totalTime);
122          dataCache.depth.push(item.depth);
123          dataCache.eventCount.push(item.eventCount);
124          dataCache.symbolId.push(item.symbolId);
125          dataCache.fileId.push(item.fileId);
126          dataCache.callchainId.push(item.callchainId);
127          dataCache.name.push(item.name);
128          let self = item.totalTime || 0;
129          if (item.children) {
130            (item.children as Array<any>).forEach((child) => {
131              self -= child.totalTime;
132            });
133          }
134          dataCache.selfDur.push(self);
135        }
136        if (item.depth + 1 > dataCache.maxDepth) {
137          dataCache.maxDepth = item.depth + 1;
138        }
139        if (item.children && item.children.length > 0) {
140          getArrayData(item.children);
141        }
142      }
143    };
144    getArrayData(result);
145  }
146  setTimeout((): void => {
147    arrayBufferCallback(data, transfer);
148  }, 150);
149}
150
151function arrayBufferCallback(data: any, transfer: boolean): void {
152  let params = data.params;
153  let dataFilter = filterPerfCallChartData(params.startNS, params.endNS, params.totalNS, params.frame, params.expand);
154  let len = dataFilter.startTs.length;
155  let perfCallChart = new PerfCallChart(len);
156  for (let i = 0; i < len; i++) {
157    perfCallChart.startTs[i] = dataFilter.startTs[i];
158    perfCallChart.dur[i] = dataFilter.dur[i];
159    perfCallChart.depth[i] = dataFilter.depth[i];
160    perfCallChart.eventCount[i] = dataFilter.eventCount[i];
161    perfCallChart.symbolId[i] = dataFilter.symbolId[i];
162    perfCallChart.fileId[i] = dataFilter.fileId[i];
163    perfCallChart.callchainId[i] = dataFilter.callchainId[i];
164    perfCallChart.selfDur[i] = dataFilter.selfDur[i];
165    perfCallChart.name[i] = dataFilter.name[i];
166  }
167  postPerfCallChartMessage(data, transfer, perfCallChart, len);
168}
169function postPerfCallChartMessage(data: any, transfer: boolean, perfCallChart: PerfCallChart, len: number) {
170  (self as unknown as Worker).postMessage(
171    {
172      id: data.id,
173      action: data.action,
174      results: transfer
175        ? {
176            startTs: perfCallChart.startTs.buffer,
177            dur: perfCallChart.dur.buffer,
178            depth: perfCallChart.depth.buffer,
179            callchainId: perfCallChart.callchainId.buffer,
180            eventCount: perfCallChart.eventCount.buffer,
181            symbolId: perfCallChart.symbolId.buffer,
182            fileId: perfCallChart.fileId.buffer,
183            selfDur: perfCallChart.selfDur.buffer,
184            name: perfCallChart.name.buffer,
185            maxDepth: dataCache.maxDepth,
186          }
187        : {},
188      len: len,
189    },
190    transfer
191      ? [
192          perfCallChart.startTs.buffer,
193          perfCallChart.dur.buffer,
194          perfCallChart.depth.buffer,
195          perfCallChart.callchainId.buffer,
196          perfCallChart.eventCount.buffer,
197          perfCallChart.symbolId.buffer,
198          perfCallChart.fileId.buffer,
199          perfCallChart.selfDur.buffer,
200          perfCallChart.name.buffer,
201        ]
202      : []
203  );
204}
205
206export function filterPerfCallChartData(
207  startNS: number,
208  endNS: number,
209  totalNS: number,
210  frame: any,
211  expand: boolean
212): DataSource {
213  let dataSource = new DataSource();
214  let data: any = {};
215  dataCache.startTs.reduce((pre, current, index) => {
216    if (
217      dataCache.dur[index] > 0 &&
218      current + dataCache.dur[index] >= startNS &&
219      current <= endNS &&
220      ((!expand && dataCache.depth[index] === 0) || expand)
221    ) {
222      let x = 0;
223      if (current > startNS && current < endNS) {
224        x = Math.trunc(ns2x(current, startNS, endNS, totalNS, frame));
225      } else {
226        x = 0;
227      }
228      let key = `${x}-${dataCache.depth[index]}`;
229      let preIndex = pre[key];
230      if (preIndex !== undefined) {
231        pre[key] = dataCache.dur[preIndex] > dataCache.dur[index] ? preIndex : index;
232      } else {
233        pre[key] = index;
234      }
235    }
236    return pre;
237  }, data);
238  setDataSource(data, dataSource);
239  return dataSource;
240}
241function setDataSource(data: any, dataSource: DataSource) {
242  Reflect.ownKeys(data).map((kv: string | symbol): void => {
243    let index = data[kv as string] as number;
244    dataSource.startTs.push(dataCache.startTs[index]);
245    dataSource.dur.push(dataCache.dur[index]);
246    dataSource.depth.push(dataCache.depth[index]);
247    dataSource.eventCount.push(dataCache.eventCount[index]);
248    dataSource.symbolId.push(dataCache.symbolId[index]);
249    dataSource.fileId.push(dataCache.fileId[index]);
250    dataSource.callchainId.push(dataCache.callchainId[index]);
251    dataSource.selfDur.push(dataCache.selfDur[index]);
252    dataSource.name.push(dataCache.name[index]);
253  });
254}
255// 将perf_sample表的数据根据callchain_id分组并赋值startTime,endTime等等
256function combinePerfSampleByCallChainId(sampleList: Array<any>, params: any): any[] {
257  return combineChartData(
258    sampleList.map((sample) => {
259      let perfSample: any = {};
260      perfSample.children = new Array<any>();
261      perfSample.children[0] = {};
262      perfSample.depth = -1;
263      perfSample.callchainId = sample.callchainId;
264      perfSample.threadId = sample.threadId;
265      perfSample.id = sample.id;
266      perfSample.cpuId = sample.cpuId;
267      perfSample.startTime = sample.startTs;
268      perfSample.endTime = sample.startTs + sample.dur;
269      perfSample.totalTime = sample.dur;
270      perfSample.eventCount = sample.eventCount;
271      return perfSample;
272    }),
273    params
274  );
275}
276
277function combineChartData(samples: any, params: any): Array<any> {
278  let combineSample: any = [];
279  // 遍历sample表查到的数据,并且为其匹配相应的callchain数据
280  for (let sample of samples) {
281    let stackTop = dataCache.callstack.get(`${sample.callchainId}-0`);
282    if (stackTop) {
283      let stackTopSymbol = JSON.parse(JSON.stringify(stackTop));
284      stackTopSymbol.startTime = sample.startTime;
285      stackTopSymbol.endTime = sample.endTime;
286      stackTopSymbol.totalTime = sample.totalTime;
287      stackTopSymbol.threadId = sample.threadId;
288      stackTopSymbol.cpuId = sample.cpuId;
289      stackTopSymbol.eventCount = sample.eventCount;
290      setDur(stackTopSymbol);
291      sample.children = new Array<any>();
292      sample.children.push(stackTopSymbol);
293      // 每一项都和combineSample对比
294      if (combineSample.length === 0) {
295        combineSample.push(sample);
296      } else {
297        let pre = combineSample[combineSample.length - 1];
298        if (params.type === 0) {
299          if (pre.threadId === sample.threadId && pre.endTime === sample.startTime) {
300            combinePerfCallData(combineSample[combineSample.length - 1], sample);
301          } else {
302            combineSample.push(sample);
303          }
304        } else {
305          if (pre.cpuId === sample.cpuId && pre.endTime === sample.startTime) {
306            combinePerfCallData(combineSample[combineSample.length - 1], sample);
307          } else {
308            combineSample.push(sample);
309          }
310        }
311      }
312    }
313  }
314  return combineSample;
315}
316
317// 递归设置dur,startTime,endTime
318function setDur(data: any): void {
319  if (data.children && data.children.length > 0) {
320    data.children[0].totalTime = data.totalTime;
321    data.children[0].startTime = data.startTime;
322    data.children[0].endTime = data.endTime;
323    data.children[0].threadId = data.threadId;
324    data.children[0].cpuId = data.cpuId;
325    data.children[0].eventCount = data.eventCount;
326    setDur(data.children[0]);
327  } else {
328    return;
329  }
330}
331
332// hiperf火焰图合并逻辑
333function combinePerfCallData(data1: any, data2: any): void {
334  if (fixMergeRuler(data1, data2)) {
335    data1.endTime = data2.endTime;
336    data1.totalTime = data1.endTime - data1.startTime;
337    data1.eventCount += data2.eventCount;
338    if (data1.children && data1.children.length > 0 && data2.children && data2.children.length > 0) {
339      if (fixMergeRuler(data1.children[data1.children.length - 1], data2.children[0])) {
340        combinePerfCallData(data1.children[data1.children.length - 1], data2.children[0]);
341      } else {
342        if (data1.children[data1.children.length - 1].depth === data2.children[0].depth) {
343          data1.children.push(data2.children[0]);
344        }
345      }
346    } else if (data2.children && data2.children.length > 0 && (!data1.children || data1.children.length === 0)) {
347      data1.endTime = data2.endTime;
348      data1.totalTime = data1.endTime - data1.startTime;
349      data1.children = new Array<any>();
350      data1.children.push(data2.children[0]);
351    } else {
352    }
353  }
354  return;
355}
356
357/**
358 * 合并规则
359 * @param data1
360 * @param data2
361 */
362function fixMergeRuler(data1: any, data2: any): boolean {
363  return data1.depth === data2.depth && data1.name === data2.name;
364}
365
366export const hiPerfCallStackDataCacheSql = (): string => {
367  return `select c.callchain_id as callchainId,
368                 c.file_id   as fileId,
369                 c.depth,
370                 c.symbol_id as symbolId,
371                 c.name
372          from perf_callchain c
373          where callchain_id != -1;`;
374};
375
376export function hiPerfCallChartClearCache(clearStack: boolean): void {
377  if (clearStack) {
378    dataCache.callstack.clear();
379    dataCache.sampleList.length = 0;
380  }
381  dataCache.startTs = [];
382  dataCache.dur = [];
383  dataCache.depth = [];
384  dataCache.eventCount = [];
385  dataCache.symbolId = [];
386  dataCache.fileId = [];
387  dataCache.callchainId = [];
388  dataCache.selfDur = [];
389  dataCache.name = [];
390  dataCache.maxDepth = 1;
391}
392
393function arrayBufferCallStackHandler(data: any, res: any[]): void {
394  for (const stack of res) {
395    let item = stack;
396    if (data.params.trafic === TraficEnum.ProtoBuffer) {
397      item = {
398        callchainId: stack.hiperfCallStackData.callchainId || 0,
399        fileId: stack.hiperfCallStackData.fileId || 0,
400        depth: stack.hiperfCallStackData.depth || 0,
401        symbolId: stack.hiperfCallStackData.symbolId || 0,
402        name: stack.hiperfCallStackData.name || 0,
403      };
404    }
405    dataCache.callstack.set(`${item.callchainId}-${item.depth}`, item);
406    let parentSymbol = dataCache.callstack.get(`${item.callchainId}-${item.depth - 1}`);
407    if (parentSymbol && parentSymbol.callchainId === item.callchainId && parentSymbol.depth === item.depth - 1) {
408      parentSymbol.children = new Array<any>();
409      parentSymbol.children.push(item);
410    }
411  }
412  for (let key of Array.from(dataCache.callstack.keys())) {
413    if (!key.endsWith('-0')) {
414      dataCache.callstack.delete(key);
415    }
416  }
417  (self as unknown as Worker).postMessage(
418    {
419      id: data.id,
420      action: data.action,
421      results: 'ok',
422      len: res.length,
423    },
424    []
425  );
426}
427
428function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: any): number {
429  if (endNS === 0) {
430    endNS = duration;
431  }
432  let xSizeHiperf: number = ((ns - startNS) * rect.width) / (endNS - startNS);
433  if (xSizeHiperf < 0) {
434    xSizeHiperf = 0;
435  } else if (xSizeHiperf > rect.width) {
436    xSizeHiperf = rect.width;
437  }
438  return xSizeHiperf;
439}
440class PerfCallChart {
441  startTs: Float64Array;
442  dur: Float64Array;
443  depth: Int32Array;
444  eventCount: Int32Array;
445  symbolId: Int32Array;
446  fileId: Int32Array;
447  callchainId: Int32Array;
448  selfDur: Int32Array;
449  name: Int32Array;
450  constructor(len: number) {
451    this.startTs = new Float64Array(len);
452    this.dur = new Float64Array(len);
453    this.depth = new Int32Array(len);
454    this.eventCount = new Int32Array(len);
455    this.symbolId = new Int32Array(len);
456    this.fileId = new Int32Array(len);
457    this.callchainId = new Int32Array(len);
458    this.selfDur = new Int32Array(len);
459    this.name = new Int32Array(len);
460  }
461}
462class DataSource {
463  startTs: Array<number>;
464  dur: Array<number>;
465  depth: Array<number>;
466  eventCount: Array<number>;
467  symbolId: Array<number>;
468  fileId: Array<number>;
469  callchainId: Array<number>;
470  selfDur: Array<number>;
471  name: Array<number>;
472  constructor() {
473    this.startTs = [];
474    this.dur = [];
475    this.depth = [];
476    this.eventCount = [];
477    this.symbolId = [];
478    this.fileId = [];
479    this.callchainId = [];
480    this.selfDur = [];
481    this.name = [];
482  }
483}
484