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 { JanksStruct } from '../../bean/JanksStruct';
17import { ColorUtils } from '../../component/trace/base/ColorUtils';
18import { TraceRow } from '../../component/trace/base/TraceRow';
19import {
20  drawLoadingFrame,
21  drawString,
22  isFrameContainPoint,
23  ns2x,
24  Rect,
25  Render,
26  RequestMessage,
27} from './ProcedureWorkerCommon';
28import { SpSystemTrace } from '../../component/SpSystemTrace';
29
30export class JankRender {
31  renderMainThread(
32    req: {
33      useCache: boolean;
34      context: CanvasRenderingContext2D;
35      type: string;
36    },
37    row: TraceRow<JankStruct>
38  ): void {
39    let jankList = row.dataList;
40    let jankFilter = row.dataListCache;
41    jank(
42      jankList,
43      jankFilter,
44      TraceRow.range!.startNS,
45      TraceRow.range!.endNS,
46      TraceRow.range!.totalNS,
47      row.frame,
48      req.useCache || !TraceRow.range!.refresh
49    );
50    drawLoadingFrame(req.context, row.dataListCache, row);
51    req.context.beginPath();
52    let find = false;
53    let nsScale = ((TraceRow.range!.endNS || 0) - (TraceRow.range!.startNS || 0)) / (TraceRow.range!.totalNS * 9);
54    for (let re of jankFilter) {
55      JankStruct.draw(req.context, re, nsScale);
56      if (row.isHover) {
57        if (re.dur === 0 || re.dur === null || re.dur === undefined) {
58          if (
59            re.frame &&
60            row.hoverX >= re.frame.x - 5 &&
61            row.hoverX <= re.frame.x + 5 &&
62            row.hoverY >= re.frame.y &&
63            row.hoverY <= re.frame.y + re.frame.height
64          ) {
65            JankStruct.hoverJankStruct = re;
66            find = true;
67          }
68        } else {
69          if (re.frame && isFrameContainPoint(re.frame, row.hoverX, row.hoverY)) {
70            JankStruct.hoverJankStruct = re;
71            find = true;
72          }
73        }
74      }
75    }
76    if (!find && row.isHover) {
77      JankStruct.hoverJankStruct = undefined;
78    }
79    req.context.closePath();
80  }
81
82  render(req: RequestMessage, list: Array<JankStruct>, filter: Array<JankStruct>): void {}
83}
84
85export function jank(
86  jankList: Array<JankStruct>,
87  jankFilter: Array<JankStruct>,
88  startNS: number,
89  endNS: number,
90  totalNS: number,
91  frame: Rect,
92  use: boolean
93): void {
94  if (use && jankFilter.length > 0) {
95    for (let i = 0, len = jankFilter.length; i < len; i++) {
96      if ((jankFilter[i].ts || 0) + (jankFilter[i].dur || 0) >= startNS && (jankFilter[i].ts || 0) <= endNS) {
97        JankStruct.setJankFrame(jankFilter[i], 0, startNS, endNS, totalNS, frame);
98      } else {
99        jankFilter[i].frame = undefined;
100      }
101    }
102    return;
103  }
104  jankFilter.length = 0;
105  if (jankList) {
106    let groups = jankList
107      .filter((it) => (it.ts ?? 0) + (it.dur ?? 0) >= startNS && (it.ts ?? 0) <= endNS)
108      .map((it) => {
109        JankStruct.setJankFrame(it, 0, startNS, endNS, totalNS, frame);
110        return it;
111      })
112      .reduce((pre, current, index, arr) => {
113        // @ts-ignore
114        (pre[`${current.frame.x}-${current.depth}`] = pre[`${current.frame.x}-${current.depth}`] || []).push(current);
115        return pre;
116      }, {});
117    Reflect.ownKeys(groups).map((kv) => {
118      // @ts-ignore
119      let arr = groups[kv].sort((a: JankStruct, b: JankStruct) => b.dur - a.dur);
120      jankFilter.push(arr[0]);
121    });
122  }
123}
124
125export function JankStructOnClick(
126  clickRowType: string,
127  sp: SpSystemTrace,
128  row: TraceRow<JankStruct>,
129  jankClickHandler: unknown,
130  entry?: JankStruct,
131): Promise<unknown> {
132  return new Promise((resolve, reject) => {
133    JankStruct.hoverJankStruct = JankStruct.hoverJankStruct || row.getHoverStruct();
134    if (clickRowType === TraceRow.ROW_TYPE_JANK && (JankStruct.hoverJankStruct || entry)) {
135      JankStruct.selectJankStructList.length = 0;
136      sp.removeLinkLinesByBusinessType('janks');
137      JankStruct.selectJankStruct = entry || JankStruct.hoverJankStruct;
138      sp.timerShaftEL?.drawTriangle(JankStruct.selectJankStruct!.ts || 0, 'inverted');
139      sp.traceSheetEL?.displayJankData(
140        JankStruct.selectJankStruct!,
141        (datas) => {
142          datas.forEach((data) => {
143            let endParentRow; // @ts-ignore
144            if (data.frameType === 'frameTime') {
145              endParentRow = sp.shadowRoot?.querySelector<TraceRow<JankStruct>>(
146                'trace-row[row-id=\'frameTime\'][row-type=\'janks\']'
147              );
148            } else {
149              endParentRow = sp.shadowRoot?.querySelector<TraceRow<JankStruct>>( // @ts-ignore
150                `trace-row[row-type='process'][row-id='${data.pid}'][folder]`
151              );
152            }
153            sp.drawJankLine(endParentRow, JankStruct.selectJankStruct!, data);
154          });
155        },
156        // @ts-ignore
157        jankClickHandler
158      );
159      reject(new Error());
160    } else {
161      resolve(null);
162    }
163  });
164}
165
166export class JankStruct extends JanksStruct {
167  static hoverJankStruct: JankStruct | undefined;
168  static selectJankStruct: JankStruct | undefined;
169  static selectJankStructList: Array<JankStruct> = [];
170
171  static setJankFrame(
172    jankNode: JankStruct,
173    padding: number,
174    startNS: number,
175    endNS: number,
176    totalNS: number,
177    frame: Rect
178  ): void {
179    let x1: number;
180    let x2: number;
181    if ((jankNode.ts || 0) > startNS && (jankNode.ts || 0) < endNS) {
182      x1 = ns2x(jankNode.ts || 0, startNS, endNS, totalNS, frame);
183    } else {
184      x1 = 0;
185    }
186    if ((jankNode.ts || 0) + (jankNode.dur || 0) > startNS && (jankNode.ts || 0) + (jankNode.dur || 0) < endNS) {
187      x2 = ns2x((jankNode.ts || 0) + (jankNode.dur || 0), startNS, endNS, totalNS, frame);
188    } else {
189      x2 = frame.width;
190    }
191    if (!jankNode.frame) {
192      jankNode.frame = new Rect(0, 0, 0, 0);
193    }
194    let getV: number = x2 - x1 < 1 ? 1 : x2 - x1;
195    jankNode.frame.x = Math.floor(x1);
196    jankNode.frame.y = jankNode.depth! * 20;
197    jankNode.frame.width = Math.ceil(getV);
198    jankNode.frame.height = 20;
199  }
200
201  static draw(ctx: CanvasRenderingContext2D, data: JankStruct, nsScale: number): void {
202    if (data.frame) {
203      if (data.dur === undefined || data.dur === null || data.dur === 0) {
204      } else {
205        ctx.globalAlpha = 1;
206        ctx.fillStyle = ColorUtils.JANK_COLOR[0];
207        if (data.jank_tag === 1) {
208          ctx.fillStyle = ColorUtils.JANK_COLOR[2];
209        } else if (data.jank_tag === 3) {
210          ctx.fillStyle = ColorUtils.JANK_COLOR[3];
211        }
212        let miniHeight = 20;
213        if (
214          JankStruct.hoverJankStruct &&
215          data.name === JankStruct.hoverJankStruct.name &&
216          JankStruct.hoverJankStruct.type === data.type &&
217          JankStruct.hoverJankStruct.pid === data.pid &&
218          JankStruct.hoverJankStruct.frameType === data.frameType
219        ) {
220          ctx.globalAlpha = 0.7;
221        }
222        if (`${data.type}` === '0') {
223          this.drawActualFrame(ctx, data, miniHeight);
224        } else {
225          this.drawExpectedFrame(data, nsScale, ctx, miniHeight);
226        }
227        if (data.frame.width > 10) {
228          ctx.fillStyle = '#fff';
229          drawString(ctx, `${data.name || ''}`, 5, data.frame, data);
230        }
231        if (JankStruct.isSelected(data)) {
232          ctx.strokeStyle = '#000';
233          ctx.lineWidth = 2;
234          ctx.strokeRect(data.frame.x, data.frame.y + 1, data.frame.width, miniHeight - padding * 2 - 2);
235        }
236      }
237    }
238  }
239
240  private static drawExpectedFrame(
241    data: JankStruct,
242    nsScale: number,
243    ctx: CanvasRenderingContext2D,
244    miniHeight: number
245  ): void {
246    if (data.frame && data.frame.width * nsScale < 1.5) {
247      ctx.fillStyle = '#FFFFFF';
248      ctx.fillRect(data.frame.x, data.frame.y, data.frame.width * nsScale, miniHeight - padding * 2);
249      ctx.fillStyle = ColorUtils.JANK_COLOR[0];
250      ctx.fillRect(
251        data.frame.x + data.frame.width * nsScale,
252        data.frame.y,
253        data.frame.width - nsScale * 2,
254        miniHeight - padding * 2
255      );
256      ctx.fillStyle = '#FFFFFF';
257      ctx.fillRect(
258        data.frame.x + data.frame.width * nsScale + data.frame.width - nsScale * 2,
259        data.frame.y,
260        data.frame.width * nsScale,
261        miniHeight - padding * 2
262      );
263    } else {
264      ctx.fillStyle = ColorUtils.JANK_COLOR[0];
265      if (data.frame) {
266        ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2);
267      }
268    }
269  }
270
271  private static drawActualFrame(ctx: CanvasRenderingContext2D, data: JankStruct, miniHeight: number): void {
272    ctx.fillStyle = ColorUtils.JANK_COLOR[0];
273    if (data.jank_tag === 1) {
274      ctx.fillStyle = ColorUtils.JANK_COLOR[2];
275    } else if (data.jank_tag === 3) {
276      ctx.fillStyle = ColorUtils.JANK_COLOR[3];
277    }
278    if (data.frame) {
279      ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2);
280    }
281  }
282
283  static isSelected(data: JankStruct): boolean {
284    return (
285      JankStruct.selectJankStruct !== undefined &&
286      JankStruct.selectJankStruct.ts === data.ts &&
287      JankStruct.selectJankStruct.type === data.type &&
288      JankStruct.selectJankStruct.pid === data.pid &&
289      JankStruct.selectJankStruct.frameType === data.frameType
290    );
291  }
292}
293
294const padding = 1;
295