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