• 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 { TraficEnum } from '../utils/QueryEnum';
15
16interface HiPerfSampleType {
17  callchainId: number;
18  startTs: number;
19  eventCount: number;
20  threadId: number;
21  cpuId: number;
22  eventTypeId: number;
23}
24
25const dataCache: {
26  startTs: Array<number>;
27  dur: Array<number>;
28  depth: Array<number>;
29  eventCount: Array<number>;
30  symbolId: Array<number>;
31  fileId: Array<number>;
32  callchainId: Array<number>;
33  selfDur: Array<number>;
34  name: Array<number>;
35  callstack: Map<string, unknown>;
36  sampleList: Array<HiPerfSampleType>;
37  maxDepth: number;
38} = {
39  callstack: new Map<string, unknown>(),
40  sampleList: [],
41  maxDepth: 1,
42  startTs: [],
43  dur: [],
44  depth: [],
45  eventCount: [],
46  symbolId: [],
47  fileId: [],
48  callchainId: [],
49  selfDur: [],
50  name: [],
51};
52
53export const chartHiperfCallChartDataSql = (args: unknown): string => {
54  const sql = `
55    select callchain_id                             as callchainId,
56           timestamp_trace - ${
57             // @ts-ignore
58             args.recordStartNS
59           }  as startTs,
60           event_count                              as eventCount,
61           A.thread_id                              as threadId,
62           cpu_id                                   as cpuId,
63           event_type_id                            as eventTypeId
64    from perf_sample A
65    where callchain_id != -1 and A.thread_id != 0
66    order by cpuId, startTs`;
67  return sql;
68};
69
70export function hiPerfCallChartDataHandler(data: unknown, proc: Function): void {
71  // @ts-ignore
72  if (data.params.isCache) {
73    // @ts-ignore
74    let res: Array<unknown> = proc(chartHiperfCallChartDataSql(data.params));
75    for (let i = 0; i < res.length; i++) {
76      if (i > 0) {
77        // @ts-ignore
78        if (res[i].cpuId === res[i - 1].cpuId) {
79          // @ts-ignore
80          res[i - 1].dur = res[i].startTs - res[i - 1].startTs;
81        } else {
82          // @ts-ignore
83          res[i - 1].dur = data.params.recordEndNS - data.params.recordStartNS - res[i - 1].startTs;
84        }
85      }
86      if (i === res.length - 1) {
87        // @ts-ignore
88        res[i].dur = data.params.recordEndNS - data.params.recordStartNS - res[i].startTs;
89      }
90    }
91    // @ts-ignore
92    dataCache.sampleList = res;
93    (self as unknown as Worker).postMessage(
94      {
95        len: 0,
96        // @ts-ignore
97        id: data.id,
98        // @ts-ignore
99        action: data.action,
100        results: 'ok',
101      },
102      []
103    );
104  } else {
105    let res: Array<unknown> = [];
106    // @ts-ignore
107    if (!data.params.isComplete) {
108      res = dataCache.sampleList.filter((it) => {
109        // @ts-ignore
110        let cpuThreadFilter = data.params.type === 0 ? it.cpuId === data.params.id : it.threadId === data.params.id;
111        // @ts-ignore
112        let eventTypeFilter = data.params.eventTypeId === -2 ? true : it.eventTypeId === data.params.eventTypeId;
113        return cpuThreadFilter && eventTypeFilter;
114      });
115    }
116    // @ts-ignore
117    arrayBufferHandler(data, res, true, !data.params.isComplete);
118  }
119}
120
121export function hiPerfCallStackCacheHandler(data: unknown, proc: Function): void {
122  // @ts-ignore
123  if (data.params.isCache) {
124    hiPerfCallChartClearCache(true);
125    arrayBufferCallStackHandler(data, proc(hiPerfCallStackDataCacheSql()));
126  }
127}
128
129function arrayBufferHandler(data: unknown, res: unknown[], transfer: boolean, loadData: boolean): void {
130  if (loadData) {
131    // @ts-ignore
132    let result = combinePerfSampleByCallChainId(res, data.params);
133    hiPerfCallChartClearCache(false);
134    const getArrayData = (combineData: Array<unknown>): void => {
135      for (let item of combineData) {
136        // @ts-ignore
137        if (item.depth > -1) {
138          // @ts-ignore
139          dataCache.startTs.push(item.startTime);
140          // @ts-ignore
141          dataCache.dur.push(item.totalTime);
142          // @ts-ignore
143          dataCache.depth.push(item.depth);
144          // @ts-ignore
145          dataCache.eventCount.push(item.eventCount);
146          // @ts-ignore
147          dataCache.symbolId.push(item.symbolId);
148          // @ts-ignore
149          dataCache.fileId.push(item.fileId);
150          // @ts-ignore
151          dataCache.callchainId.push(item.callchainId);
152          // @ts-ignore
153          dataCache.name.push(item.name);
154          // @ts-ignore
155          let self = item.totalTime || 0;
156          // @ts-ignore
157          if (item.children) {
158            // @ts-ignore
159            (item.children as Array<unknown>).forEach((child) => {
160              // @ts-ignore
161              self -= child.totalTime;
162            });
163          }
164          dataCache.selfDur.push(self);
165        }
166        // @ts-ignore
167        if (item.depth + 1 > dataCache.maxDepth) {
168          // @ts-ignore
169          dataCache.maxDepth = item.depth + 1;
170        }
171        // @ts-ignore
172        if (item.children && item.children.length > 0) {
173          // @ts-ignore
174          getArrayData(item.children);
175        }
176      }
177    };
178    getArrayData(result);
179  }
180  setTimeout((): void => {
181    arrayBufferCallback(data, transfer);
182  }, 150);
183}
184
185function arrayBufferCallback(data: unknown, transfer: boolean): void {
186  // @ts-ignore
187  let params = data.params;
188  let dataFilter = filterPerfCallChartData(params.startNS, params.endNS, params.totalNS, params.frame, params.expand);
189  let len = dataFilter.startTs.length;
190  let perfCallChart = new PerfCallChart(len);
191  for (let i = 0; i < len; i++) {
192    perfCallChart.startTs[i] = dataFilter.startTs[i];
193    perfCallChart.dur[i] = dataFilter.dur[i];
194    perfCallChart.depth[i] = dataFilter.depth[i];
195    perfCallChart.eventCount[i] = dataFilter.eventCount[i];
196    perfCallChart.symbolId[i] = dataFilter.symbolId[i];
197    perfCallChart.fileId[i] = dataFilter.fileId[i];
198    perfCallChart.callchainId[i] = dataFilter.callchainId[i];
199    perfCallChart.selfDur[i] = dataFilter.selfDur[i];
200    perfCallChart.name[i] = dataFilter.name[i];
201  }
202  postPerfCallChartMessage(data, transfer, perfCallChart, len);
203}
204function postPerfCallChartMessage(data: unknown, transfer: boolean, perfCallChart: PerfCallChart, len: number): void {
205  (self as unknown as Worker).postMessage(
206    {
207      // @ts-ignore
208      id: data.id,
209      // @ts-ignore
210      action: data.action,
211      results: transfer
212        ? {
213            startTs: perfCallChart.startTs.buffer,
214            dur: perfCallChart.dur.buffer,
215            depth: perfCallChart.depth.buffer,
216            callchainId: perfCallChart.callchainId.buffer,
217            eventCount: perfCallChart.eventCount.buffer,
218            symbolId: perfCallChart.symbolId.buffer,
219            fileId: perfCallChart.fileId.buffer,
220            selfDur: perfCallChart.selfDur.buffer,
221            name: perfCallChart.name.buffer,
222            maxDepth: dataCache.maxDepth,
223          }
224        : {},
225      len: len,
226    },
227    transfer
228      ? [
229          perfCallChart.startTs.buffer,
230          perfCallChart.dur.buffer,
231          perfCallChart.depth.buffer,
232          perfCallChart.callchainId.buffer,
233          perfCallChart.eventCount.buffer,
234          perfCallChart.symbolId.buffer,
235          perfCallChart.fileId.buffer,
236          perfCallChart.selfDur.buffer,
237          perfCallChart.name.buffer,
238        ]
239      : []
240  );
241}
242
243export function filterPerfCallChartData(
244  startNS: number,
245  endNS: number,
246  totalNS: number,
247  frame: unknown,
248  expand: boolean
249): DataSource {
250  let dataSource = new DataSource();
251  let data: unknown = {};
252  dataCache.startTs.reduce((pre, current, index) => {
253    if (
254      dataCache.dur[index] > 0 &&
255      current + dataCache.dur[index] >= startNS &&
256      current <= endNS &&
257      ((!expand && dataCache.depth[index] === 0) || expand)
258    ) {
259      let x = 0;
260      if (current > startNS && current < endNS) {
261        x = Math.trunc(ns2x(current, startNS, endNS, totalNS, frame));
262      } else {
263        x = 0;
264      }
265      let key = `${x}-${dataCache.depth[index]}`;
266      // @ts-ignore
267      let preIndex = pre[key];
268      if (preIndex !== undefined) {
269        // @ts-ignore
270        pre[key] = dataCache.dur[preIndex] > dataCache.dur[index] ? preIndex : index;
271      } else {
272        // @ts-ignore
273        pre[key] = index;
274      }
275    }
276    return pre;
277  }, data);
278  setDataSource(data, dataSource);
279  return dataSource;
280}
281function setDataSource(data: unknown, dataSource: DataSource): void {
282  // @ts-ignore
283  Reflect.ownKeys(data).map((kv: string | symbol): void => {
284    // @ts-ignore
285    let index = data[kv as string] as number;
286    // @ts-ignore
287    dataSource.startTs.push(dataCache.startTs[index]);
288    dataSource.dur.push(dataCache.dur[index]);
289    dataSource.depth.push(dataCache.depth[index]);
290    dataSource.eventCount.push(dataCache.eventCount[index]);
291    dataSource.symbolId.push(dataCache.symbolId[index]);
292    dataSource.fileId.push(dataCache.fileId[index]);
293    dataSource.callchainId.push(dataCache.callchainId[index]);
294    dataSource.selfDur.push(dataCache.selfDur[index]);
295    dataSource.name.push(dataCache.name[index]);
296  });
297}
298// 将perf_sample表的数据根据callchain_id分组并赋值startTime,endTime等等
299function combinePerfSampleByCallChainId(sampleList: Array<unknown>, params: unknown): unknown[] {
300  return combineChartData(
301    sampleList.map((sample) => {
302      let perfSample: unknown = {};
303      // @ts-ignore
304      perfSample.children = [];
305      // @ts-ignore
306      perfSample.children[0] = {};
307      // @ts-ignore
308      perfSample.depth = -1;
309      // @ts-ignore
310      perfSample.callchainId = sample.callchainId;
311      // @ts-ignore
312      perfSample.threadId = sample.threadId;
313      // @ts-ignore
314      perfSample.id = sample.id;
315      // @ts-ignore
316      perfSample.cpuId = sample.cpuId;
317      // @ts-ignore
318      perfSample.startTime = sample.startTs;
319      // @ts-ignore
320      perfSample.endTime = sample.startTs + sample.dur;
321      // @ts-ignore
322      perfSample.totalTime = sample.dur;
323      // @ts-ignore
324      perfSample.eventCount = sample.eventCount;
325      return perfSample;
326    }),
327    params
328  );
329}
330
331function combineChartData(samples: unknown, params: unknown): Array<unknown> {
332  let combineSample: unknown = [];
333  // 遍历sample表查到的数据,并且为其匹配相应的callchain数据
334  // @ts-ignore
335  for (let sample of samples) {
336    let stackTop = dataCache.callstack.get(`${sample.callchainId}-0`);
337    if (stackTop) {
338      let stackTopSymbol = JSON.parse(JSON.stringify(stackTop));
339      stackTopSymbol.startTime = sample.startTime;
340      stackTopSymbol.endTime = sample.endTime;
341      stackTopSymbol.totalTime = sample.totalTime;
342      stackTopSymbol.threadId = sample.threadId;
343      stackTopSymbol.cpuId = sample.cpuId;
344      stackTopSymbol.eventCount = sample.eventCount;
345      setDur(stackTopSymbol);
346      sample.children = [];
347      sample.children.push(stackTopSymbol);
348      // 每一项都和combineSample对比
349      // @ts-ignore
350      if (combineSample.length === 0) {
351        // @ts-ignore
352        combineSample.push(sample);
353      } else {
354        // @ts-ignore
355        let pre = combineSample[combineSample.length - 1];
356        // @ts-ignore
357        if (params.type === 0) {
358          if (pre.threadId === sample.threadId && pre.endTime === sample.startTime) {
359            // @ts-ignore
360            combinePerfCallData(combineSample[combineSample.length - 1], sample);
361          } else {
362            // @ts-ignore
363            combineSample.push(sample);
364          }
365        } else {
366          if (pre.cpuId === sample.cpuId && pre.endTime === sample.startTime) {
367            // @ts-ignore
368            combinePerfCallData(combineSample[combineSample.length - 1], sample);
369          } else {
370            // @ts-ignore
371            combineSample.push(sample);
372          }
373        }
374      }
375    }
376  }
377  // @ts-ignore
378  return combineSample;
379}
380
381// 递归设置dur,startTime,endTime
382function setDur(data: unknown): void {
383  // @ts-ignore
384  if (data.children && data.children.length > 0) {
385    // @ts-ignore
386    data.children[0].totalTime = data.totalTime;
387    // @ts-ignore
388    data.children[0].startTime = data.startTime;
389    // @ts-ignore
390    data.children[0].endTime = data.endTime;
391    // @ts-ignore
392    data.children[0].threadId = data.threadId;
393    // @ts-ignore
394    data.children[0].cpuId = data.cpuId;
395    // @ts-ignore
396    data.children[0].eventCount = data.eventCount;
397    // @ts-ignore
398    setDur(data.children[0]);
399  } else {
400    return;
401  }
402}
403
404// hiperf火焰图合并逻辑
405function combinePerfCallData(data1: unknown, data2: unknown): void {
406  if (fixMergeRuler(data1, data2)) {
407    // @ts-ignore
408    data1.endTime = data2.endTime;
409    // @ts-ignore
410    data1.totalTime = data1.endTime - data1.startTime;
411    // @ts-ignore
412    data1.eventCount += data2.eventCount;
413    // @ts-ignore
414    if (data1.children && data1.children.length > 0 && data2.children && data2.children.length > 0) {
415      // @ts-ignore
416      if (fixMergeRuler(data1.children[data1.children.length - 1], data2.children[0])) {
417        // @ts-ignore
418        combinePerfCallData(data1.children[data1.children.length - 1], data2.children[0]);
419      } else {
420        // @ts-ignore
421        if (data1.children[data1.children.length - 1].depth === data2.children[0].depth) {
422          // @ts-ignore
423          data1.children.push(data2.children[0]);
424        }
425      }
426      // @ts-ignore
427    } else if (data2.children && data2.children.length > 0 && (!data1.children || data1.children.length === 0)) {
428      // @ts-ignore
429      data1.endTime = data2.endTime;
430      // @ts-ignore
431      data1.totalTime = data1.endTime - data1.startTime;
432      // @ts-ignore
433      data1.children = [];
434      // @ts-ignore
435      data1.children.push(data2.children[0]);
436    } else {
437    }
438  }
439  return;
440}
441
442/**
443 * 合并规则
444 * @param data1
445 * @param data2
446 */
447function fixMergeRuler(data1: unknown, data2: unknown): boolean {
448  // @ts-ignore
449  return data1.depth === data2.depth && data1.name === data2.name;
450}
451
452export const hiPerfCallStackDataCacheSql = (): string => {
453  return `select c.callchain_id as callchainId,
454                 c.file_id   as fileId,
455                 c.depth,
456                 c.symbol_id as symbolId,
457                 c.name
458          from perf_callchain c
459          where callchain_id != -1;`;
460};
461
462export function hiPerfCallChartClearCache(clearStack: boolean): void {
463  if (clearStack) {
464    dataCache.callstack.clear();
465    dataCache.sampleList.length = 0;
466  }
467  dataCache.startTs = [];
468  dataCache.dur = [];
469  dataCache.depth = [];
470  dataCache.eventCount = [];
471  dataCache.symbolId = [];
472  dataCache.fileId = [];
473  dataCache.callchainId = [];
474  dataCache.selfDur = [];
475  dataCache.name = [];
476  dataCache.maxDepth = 1;
477}
478
479function arrayBufferCallStackHandler(data: unknown, res: unknown[]): void {
480  for (const stack of res) {
481    let item = stack;
482    // @ts-ignore
483    if (data.params.trafic === TraficEnum.ProtoBuffer) {
484      item = {
485        // @ts-ignore
486        callchainId: stack.hiperfCallStackData.callchainId || 0,
487        // @ts-ignore
488        fileId: stack.hiperfCallStackData.fileId || 0,
489        // @ts-ignore
490        depth: stack.hiperfCallStackData.depth || 0,
491        // @ts-ignore
492        symbolId: stack.hiperfCallStackData.symbolId || 0,
493        // @ts-ignore
494        name: stack.hiperfCallStackData.name || 0,
495      };
496    }
497    // @ts-ignore
498    dataCache.callstack.set(`${item.callchainId}-${item.depth}`, item);
499    // @ts-ignore
500    let parentSymbol = dataCache.callstack.get(`${item.callchainId}-${item.depth - 1}`);
501    // @ts-ignore
502    if (parentSymbol && parentSymbol.callchainId === item.callchainId && parentSymbol.depth === item.depth - 1) {
503      // @ts-ignore
504      parentSymbol.children = [];
505      // @ts-ignore
506      parentSymbol.children.push(item);
507    }
508  }
509  for (let key of Array.from(dataCache.callstack.keys())) {
510    if (!key.endsWith('-0')) {
511      dataCache.callstack.delete(key);
512    }
513  }
514  (self as unknown as Worker).postMessage(
515    {
516      // @ts-ignore
517      id: data.id,
518      // @ts-ignore
519      action: data.action,
520      results: 'ok',
521      len: res.length,
522    },
523    []
524  );
525}
526
527function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: unknown): number {
528  if (endNS === 0) {
529    endNS = duration;
530  }
531  // @ts-ignore
532  let xSizeHiperf: number = ((ns - startNS) * rect.width) / (endNS - startNS);
533  if (xSizeHiperf < 0) {
534    xSizeHiperf = 0;
535    // @ts-ignore
536  } else if (xSizeHiperf > rect.width) {
537    // @ts-ignore
538    xSizeHiperf = rect.width;
539  }
540  return xSizeHiperf;
541}
542class PerfCallChart {
543  startTs: Float64Array;
544  dur: Float64Array;
545  depth: Int32Array;
546  eventCount: Int32Array;
547  symbolId: Int32Array;
548  fileId: Int32Array;
549  callchainId: Int32Array;
550  selfDur: Int32Array;
551  name: Int32Array;
552  constructor(len: number) {
553    this.startTs = new Float64Array(len);
554    this.dur = new Float64Array(len);
555    this.depth = new Int32Array(len);
556    this.eventCount = new Int32Array(len);
557    this.symbolId = new Int32Array(len);
558    this.fileId = new Int32Array(len);
559    this.callchainId = new Int32Array(len);
560    this.selfDur = new Int32Array(len);
561    this.name = new Int32Array(len);
562  }
563}
564class DataSource {
565  startTs: Array<number>;
566  dur: Array<number>;
567  depth: Array<number>;
568  eventCount: Array<number>;
569  symbolId: Array<number>;
570  fileId: Array<number>;
571  callchainId: Array<number>;
572  selfDur: Array<number>;
573  name: Array<number>;
574  constructor() {
575    this.startTs = [];
576    this.dur = [];
577    this.depth = [];
578    this.eventCount = [];
579    this.symbolId = [];
580    this.fileId = [];
581    this.callchainId = [];
582    this.selfDur = [];
583    this.name = [];
584  }
585}
586