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 { BaseStruct, drawLoadingFrame, PerfRender, Rect, RequestMessage } from './ProcedureWorkerCommon';
17import { TraceRow } from '../../component/trace/base/TraceRow';
18
19export class EBPFRender extends PerfRender {
20  renderMainThread(
21    req: {
22      context: CanvasRenderingContext2D;
23      useCache: boolean;
24      type: string;
25      chartColor: string;
26    },
27    eBPFtemRow: TraceRow<EBPFChartStruct>
28  ): void {
29    let filter = eBPFtemRow.dataListCache;
30    let groupBy10MS = (TraceRow.range?.scale || 50) > 40_000_000;
31    let isDiskIO: boolean = req.type.includes('disk-io');
32    eBPFChart(
33      filter,
34      TraceRow.range?.startNS ?? 0,
35      TraceRow.range?.endNS ?? 0,
36      TraceRow.range?.totalNS ?? 0, // @ts-ignore
37      eBPFtemRow.frame,
38      groupBy10MS,
39      isDiskIO,
40      req.useCache || (TraceRow.range?.refresh ?? false)
41    );
42    drawLoadingFrame(req.context, filter, eBPFtemRow);
43    drawEBPF(req, filter, groupBy10MS, eBPFtemRow);
44  }
45
46  render(
47    eBPFRequest: RequestMessage,
48    list: Array<EBPFChartStruct>,
49    filter: Array<EBPFChartStruct>,
50    dataList2: Array<EBPFChartStruct>
51  ): void {}
52}
53
54function drawEBPF(
55  req: {
56    context: CanvasRenderingContext2D;
57    useCache: boolean;
58    type: string;
59    chartColor: string;
60  },
61  filter: EBPFChartStruct[],
62  groupBy10MS: boolean,
63  eBPFtemRow: TraceRow<EBPFChartStruct>
64): void {
65  req.context.beginPath();
66  let find = false;
67  let hoverRect: EBPFChartStruct | undefined = undefined;
68  for (let re of filter) {
69    re.group10Ms = groupBy10MS;
70    if (
71      eBPFtemRow.isHover &&
72      re.frame &&
73      eBPFtemRow.hoverX >= re.frame.x &&
74      eBPFtemRow.hoverX <= re.frame.x + re.frame.width
75    ) {
76      if (hoverRect === undefined || re.size! > hoverRect.size!) {
77        hoverRect = re;
78        find = true;
79      }
80    }
81    if (re.frame && re.frame!.x > eBPFtemRow.hoverX + 3) {
82      break;
83    }
84  }
85  if (hoverRect) {
86    EBPFChartStruct.hoverEBPFStruct = hoverRect;
87  }
88
89  for (let re of filter) {
90    EBPFChartStruct.draw(req.context, re, req.chartColor);
91  }
92  if (!find && eBPFtemRow.isHover) {
93    EBPFChartStruct.hoverEBPFStruct = undefined;
94  }
95  req.context.closePath();
96}
97
98export function eBPFChart(
99  eBPFFilters: Array<EBPFChartStruct>,
100  startNS: number,
101  endNS: number,
102  totalNS: number,
103  frame: Rect,
104  groupBy10MS: boolean,
105  isDiskIO: boolean,
106  use: boolean
107): void {
108  if (use && eBPFFilters.length > 0 && groupBy10MS) {
109    setFrameGroupBy10MS(eBPFFilters, startNS, endNS, frame);
110    return;
111  }
112  if (!groupBy10MS && eBPFFilters[0] && eBPFFilters[0].dur && eBPFFilters[0].endNS) {
113    setFrameByArr(eBPFFilters, startNS, endNS, frame, totalNS, isDiskIO);
114  }
115}
116
117function setFrameGroupBy10MS(eBPFFilters: Array<EBPFChartStruct>, startNS: number, endNS: number, frame: Rect): void {
118  let pns = (endNS - startNS) / frame.width;
119  let y = frame.y;
120  for (let i = 0; i < eBPFFilters.length; i++) {
121    let it = eBPFFilters[i];
122    if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
123      if (!it.frame) {
124        it.frame = new Rect(0, 0, 0, 0);
125        it.frame.y = y;
126      }
127      it.frame.height = it.height!;
128      EBPFChartStruct.setFrame(it, pns, startNS, endNS, frame, true);
129    } else {
130      it.frame = undefined;
131    }
132  }
133}
134
135function setFrameByArr(
136  eBPFFilters: Array<EBPFChartStruct>,
137  startNS: number,
138  endNS: number,
139  frame: Rect,
140  totalNS: number,
141  isDiskIO: boolean
142): void {
143  let list: Array<EBPFChartStruct> = [];
144  let pns = (endNS - startNS) / frame.width;
145  let y = frame.y;
146  let filter: EBPFChartStruct[] = [];
147  for (let index = 0; index < eBPFFilters.length; index++) {
148    if (
149      eBPFFilters[index].endNS! > startNS &&
150      (eBPFFilters[index].startNS || 0) < endNS &&
151      eBPFFilters[index].dur! > 0
152    ) {
153      if (index >= 1 && eBPFFilters[index - 1].endNS === eBPFFilters[index].startNS) {
154        continue;
155      } else {
156        eBPFFilters[index].size = 0;
157        filter.push(eBPFFilters[index]);
158      }
159    }
160  }
161  eBPFFilters.length = 0;
162  list = isDiskIO
163    ? (EBPFChartStruct.computeHeightNoGroupLatency(filter, totalNS) as Array<EBPFChartStruct>)
164    : (EBPFChartStruct.computeHeightNoGroup(filter, totalNS) as Array<EBPFChartStruct>);
165  list.map((it) => {
166    if (!it.frame) {
167      it.frame = new Rect(0, 0, 0, 0);
168      it.frame.y = y;
169    }
170    if (it.size && it.size > 0) {
171      EBPFChartStruct.setFrame(it, pns, startNS, endNS, frame, false);
172      eBPFFilters.push(it);
173    }
174  });
175}
176
177export class EBPFChartStruct extends BaseStruct {
178  static hoverEBPFStruct: EBPFChartStruct | undefined;
179  startNS: number | undefined;
180  endNS: number | undefined;
181  dur: number | undefined;
182  size: number | undefined;
183  height: number | undefined;
184  group10Ms: boolean | undefined;
185
186  static draw(ctx: CanvasRenderingContext2D, data: EBPFChartStruct, chartColor: string): void {
187    if (data.frame) {
188      ctx.fillStyle = chartColor;
189      ctx.strokeStyle = chartColor;
190      ctx.fillRect(data.frame.x, 40 - (data.height || 0), data.frame.width, data.height || 0);
191    }
192  }
193
194  static setFrame(
195    eBPFtemNode: EBPFChartStruct,
196    pns: number,
197    startNS: number,
198    endNS: number,
199    frame: Rect,
200    groupBy10MS: boolean
201  ): void {
202    if ((eBPFtemNode.startNS || 0) < startNS) {
203      eBPFtemNode.frame!.x = 0;
204    } else {
205      eBPFtemNode.frame!.x = Math.floor(((eBPFtemNode.startNS || 0) - startNS) / pns);
206    }
207    if ((eBPFtemNode.startNS || 0) + (eBPFtemNode.dur || 0) > endNS) {
208      eBPFtemNode.frame!.width = frame.width - eBPFtemNode.frame!.x;
209    } else {
210      if (groupBy10MS) {
211        eBPFtemNode.frame!.width = Math.ceil(((eBPFtemNode.endNS || 0) - (eBPFtemNode.startNS || 0)) / pns);
212      } else {
213        eBPFtemNode.frame!.width = Math.ceil(
214          ((eBPFtemNode.startNS || 0) + (eBPFtemNode.dur || 0) - startNS) / pns - eBPFtemNode.frame!.x
215        );
216      }
217    }
218    if (eBPFtemNode.frame!.width < 1) {
219      eBPFtemNode.frame!.width = 1;
220    }
221  }
222
223  static computeHeightNoGroup(array: Array<EBPFChartStruct>, totalNS: number): Array<unknown> {
224    if (array.length > 0) {
225      let time: Array<{ time: number; type: number }> = [];
226      array.map((item) => {
227        time.push({ time: item.startNS!, type: 1 });
228        time.push({ time: item.endNS || totalNS, type: -1 });
229      });
230      time = time.sort((a, b) => a.time - b.time);
231      let arr: Array<{
232        startNS: number;
233        dur: number;
234        size: number;
235        group10Ms: boolean;
236        height: number;
237      }> = [];
238      let first = {
239        startNS: time[0].time ?? 0,
240        dur: 0,
241        size: 1,
242        group10Ms: false,
243        height: 1,
244      };
245      arr.push(first);
246      let max = 2;
247      for (let i = 1, len = time.length; i < len; i++) {
248        let heap = {
249          startNS: time[i].time,
250          dur: 0,
251          size: 0,
252          group10Ms: false,
253          height: 0,
254        };
255        arr[i - 1].dur = heap.startNS - arr[i - 1].startNS;
256        if (i === len - 1) {
257          heap.dur = totalNS - heap.startNS;
258        }
259        heap.size = arr[i - 1].size + time[i].type;
260        heap.height = Math.floor((heap.size / 6) * 36);
261        max = max > heap.size ? max : heap.size;
262        arr.push(heap);
263      }
264      arr.map((it) => (it.height = Math.floor((it.size / max) * 36)));
265      return arr;
266    } else {
267      return [];
268    }
269  }
270
271  static computeHeightNoGroupLatency(array: Array<EBPFChartStruct>, totalNS: number): Array<unknown> {
272    if (array.length > 0) {
273      let max = 0;
274      let arr: Array<{ startNS: number; dur: number; size: number; group10Ms: boolean; height: number }> = [];
275      for (let io of array) {
276        let ioItem = {
277          startNS: io.startNS!,
278          dur: io.endNS! > totalNS ? totalNS - io.startNS! : io.endNS! - io.startNS!,
279          size: io.dur!,
280          group10Ms: false,
281          height: 0,
282        };
283        max = max > ioItem.size! ? max : ioItem.size!;
284        arr.push(ioItem);
285      }
286      arr.map((it) => {
287        let height = Math.floor((it.size / max) * 36);
288        it.height = height < 1 ? 1 : height;
289      });
290      return arr;
291    } else {
292      return [];
293    }
294  }
295}
296