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 { ColorUtils } from '../../component/trace/base/ColorUtils';
17import { TraceRow } from '../../component/trace/base/TraceRow';
18import {
19  isFrameContainPoint,
20  ns2x,
21  Render,
22  RequestMessage,
23  drawFunString,
24  drawLoadingFrame,
25  Rect,
26} from './ProcedureWorkerCommon';
27import { FuncStruct as BaseFuncStruct } from '../../bean/FuncStruct';
28import { FlagsConfig } from '../../component/SpFlags';
29import { TabPaneTaskFrames } from '../../component/trace/sheet/task/TabPaneTaskFrames';
30import { SpSystemTrace } from '../../component/SpSystemTrace';
31import { Utils } from '../../component/trace/base/Utils';
32
33export class FuncRender {
34  renderMainThread(
35    req: { useCache: boolean; context: CanvasRenderingContext2D; type: string },
36    row: TraceRow<FuncStruct>
37  ): void {
38    let funcList = row.dataList;
39    let funcFilter = row.dataListCache;
40    func(
41      funcList,
42      funcFilter,
43      TraceRow.range!.startNS,
44      TraceRow.range!.endNS,
45      TraceRow.range!.totalNS,
46      row.frame,
47      req.useCache || !TraceRow.range!.refresh,
48      row.funcExpand,
49      row.rowParentId
50    );
51    drawLoadingFrame(req.context, funcFilter, row, true);
52    req.context.beginPath();
53    let funcFind = false;
54    let flagConfig = FlagsConfig.getFlagsConfig('TaskPool');
55    for (let re of funcFilter) {
56      FuncStruct.draw(req.context, re, flagConfig);
57      if (row.isHover) {
58        if (re.dur === 0 || re.dur === null || re.dur === undefined) {
59          if (
60            re.frame &&
61            re.itid &&
62            row.hoverX >= re.frame.x - 5 &&
63            row.hoverX <= re.frame.x + 5 &&
64            row.hoverY >= re.frame.y &&
65            row.hoverY <= re.frame.y + re.frame.height
66          ) {
67            FuncStruct.hoverFuncStruct = re;
68            funcFind = true;
69          }
70        } else {
71          if (re.frame && re.itid && isFrameContainPoint(re.frame, row.hoverX, row.hoverY)) {
72            FuncStruct.hoverFuncStruct = re;
73            funcFind = true;
74          }
75        }
76      }
77    }
78    if (!funcFind && row.isHover) {
79      FuncStruct.hoverFuncStruct = undefined;
80    }
81    req.context.closePath();
82  }
83
84  render(req: RequestMessage, list: Array<FuncStruct>, filter: Array<FuncStruct>): void { }
85}
86
87export function func(
88  funcList: Array<FuncStruct>,
89  funcFilter: Array<FuncStruct>,
90  startNS: number,
91  endNS: number,
92  totalNS: number,
93  frame: Rect,
94  use: boolean,
95  expand: boolean,
96  rowParentId: string | null | undefined
97): void {
98  if (use && funcFilter.length > 0) {
99    if (rowParentId === 'UserPluginsRows' && !expand) {
100      funcFilter = funcFilter.filter((it) => it.depth === 0);
101    }
102    for (let i = 0, len = funcFilter.length; i < len; i++) {
103      if ((funcFilter[i].startTs || 0) + (funcFilter[i].dur || 0) >= startNS && (funcFilter[i].startTs || 0) <= endNS) {
104        FuncStruct.setFuncFrame(funcFilter[i], 0, startNS, endNS, totalNS, frame);
105      } else {
106        funcFilter[i].frame = undefined;
107      }
108    }
109    return;
110  }
111  funcFilter.length = 0;
112  if (funcList) {
113    let groups = funcList
114      .filter(
115        (it) =>
116          (it.startTs ?? 0) + (it.dur ?? 0) >= startNS &&
117          (it.startTs ?? 0) <= endNS &&
118          ((!expand && it.depth === 0) || expand)
119      )
120      .map((it) => {
121        FuncStruct.setFuncFrame(it, 0, startNS, endNS, totalNS, frame);
122        return it;
123      })
124      .reduce((pre, current, index, arr) => {
125        //@ts-ignore
126        (pre[`${current.frame.x}-${current.depth}`] = pre[`${current.frame.x}-${current.depth}`] || []).push(current);
127        return pre;
128      }, {});
129    Reflect.ownKeys(groups).map((kv) => {
130      //@ts-ignore
131      let arr = groups[kv].sort((a: FuncStruct, b: FuncStruct) => b.dur - a.dur);
132      funcFilter.push(arr[0]);
133    });
134  }
135}
136export function funcStructOnClick(
137  clickRowType: string,
138  sp: SpSystemTrace,
139  row: TraceRow<FuncStruct> | undefined,
140  scrollToFuncHandler: Function,
141  entry?: FuncStruct
142): Promise<unknown> {
143  return new Promise((resolve, reject) => {
144    if (clickRowType === TraceRow.ROW_TYPE_FUNC && (FuncStruct.hoverFuncStruct || entry)) {
145      if (FuncStruct.funcSelect) {
146        sp.observerScrollHeightEnable = false;
147        TabPaneTaskFrames.TaskArray = [];
148        sp.removeLinkLinesByBusinessType('task');
149        FuncStruct.firstSelectFuncStruct = FuncStruct.selectFuncStruct;
150        let hoverFuncStruct = entry || FuncStruct.hoverFuncStruct;
151        FuncStruct.selectFuncStruct = hoverFuncStruct;
152        sp.timerShaftEL?.drawTriangle(FuncStruct.selectFuncStruct!.startTs || 0, 'inverted');
153        TraceRow.rangeSelectObject = undefined;
154        let flagConfig = FlagsConfig.getFlagsConfig('TaskPool');
155        let showTabArray: Array<string> = ['current-selection'];
156        if (flagConfig!.TaskPool === 'Enabled') {
157          if (FuncStruct.selectFuncStruct?.funName) {
158            if (FuncStruct.selectFuncStruct.funName.indexOf('H:Task ') >= 0) {
159              showTabArray.push('box-task-frames');
160              sp.drawTaskPollLine(row);
161            }
162          }
163        }
164        sp.traceSheetEL?.displayFuncData(
165          showTabArray,
166          // @ts-ignore
167          row?.namePrefix,
168          FuncStruct.selectFuncStruct!,
169          scrollToFuncHandler,
170          (datas: unknown, str: string, binderTid: number) => {
171            sp.removeLinkLinesByBusinessType('func');
172            if (str === 'binder-to') {
173              //@ts-ignore
174              datas.forEach((data: { tid: unknown; pid: unknown }) => {
175                //@ts-ignore
176                let endParentRow = sp.shadowRoot?.querySelector<TraceRow<unknown>>(
177                  `trace-row[row-id='${data.pid}'][folder]`
178                );
179                sp.drawFuncLine(endParentRow, hoverFuncStruct, data, binderTid);
180              });
181            }
182          },
183          (dataList: FuncStruct[]): void => {
184            dataList.sort((leftData: FuncStruct, rightData: FuncStruct) => leftData.ts! - rightData.ts!);
185            FuncStruct.selectLineFuncStruct = dataList;
186            sp.resetDistributedLine();
187          }
188        );
189        sp.refreshCanvas(true);
190        sp.timerShaftEL?.modifyFlagList(undefined);
191      }
192      reject(new Error());
193    } else {
194      resolve(null);
195    }
196  });
197}
198export class FuncStruct extends BaseFuncStruct {
199  static textColor: string;
200  [x: string]: unknown;
201  static hoverFuncStruct: FuncStruct | undefined;
202  static selectFuncStruct: FuncStruct | undefined;
203  static selectLineFuncStruct: Array<FuncStruct> = [];
204  static firstSelectFuncStruct: FuncStruct | undefined;
205  flag: string | undefined; // 570000
206  textMetricsWidth: number | undefined;
207  static funcSelect: boolean = true;
208  pid: number | undefined;
209  static setFuncFrame(
210    funcNode: FuncStruct,
211    padding: number,
212    startNS: number,
213    endNS: number,
214    totalNS: number,
215    frame: Rect
216  ): void {
217    let x1: number;
218    let x2: number;
219    if ((funcNode.startTs || 0) > startNS && (funcNode.startTs || 0) <= endNS) {
220      x1 = ns2x(funcNode.startTs || 0, startNS, endNS, totalNS, frame);
221    } else {
222      x1 = 0;
223    }
224    if (
225      (funcNode.startTs || 0) + (funcNode.dur || 0) > startNS &&
226      (funcNode.startTs || 0) + (funcNode.dur || 0) <= endNS
227    ) {
228      x2 = ns2x((funcNode.startTs || 0) + (funcNode.dur || 0), startNS, endNS, totalNS, frame);
229    } else {
230      x2 = frame.width;
231    }
232    if (!funcNode.frame) {
233      funcNode.frame = new Rect(0, 0, 0, 0);
234    }
235    let getV: number = x2 - x1 < 1 ? 1 : x2 - x1;
236    funcNode.frame.x = Math.floor(x1);
237    funcNode.frame.y = funcNode.depth! * 18 + 3;
238    funcNode.frame.width = Math.ceil(getV);
239    funcNode.frame.height = 18;
240  }
241
242  static draw(ctx: CanvasRenderingContext2D, data: FuncStruct, flagConfig?: unknown): void {
243    if (data.frame) {
244      if (data.dur === undefined || data.dur === null) {
245      } else {
246        ctx.globalAlpha = 1;
247        //@ts-ignore
248        if (Utils.getInstance().getCallStatckMap().get(data.funName) !== undefined) {
249          //@ts-ignore
250          ctx.fillStyle = ColorUtils.FUNC_COLOR[Utils.getInstance().getCallStatckMap().get(data.funName)];
251          //@ts-ignore
252          this.textColor = ColorUtils.FUNC_COLOR[Utils.getInstance().getCallStatckMap().get(data.funName)];
253        } else {
254          ctx.fillStyle = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)];
255          this.textColor = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)];
256        }
257        if (FuncStruct.hoverFuncStruct && data.funName === FuncStruct.hoverFuncStruct.funName) {
258          ctx.globalAlpha = 0.7;
259        }
260        ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height);
261        if (data.frame.width > 10) {
262          ctx.fillStyle = ColorUtils.funcTextColor(this.textColor);
263          ctx.textBaseline = 'middle';
264          drawFunString(ctx, `${data.funName || ''}`, 5, data.frame, data);
265        }
266        if (
267          data.callid === FuncStruct.selectFuncStruct?.callid &&
268          data.startTs === FuncStruct.selectFuncStruct?.startTs &&
269          data.depth === FuncStruct.selectFuncStruct?.depth
270        ) {
271          ctx.strokeStyle = '#000';
272          ctx.lineWidth = 2;
273          ctx.strokeRect(data.frame.x, data.frame.y + 1, data.frame.width, data.frame.height - 2);
274        }
275        //@ts-ignore
276        if (flagConfig!.TaskPool === 'Enabled') {
277          if (data.funName!.indexOf('H:Task PerformTask End:') >= 0 && data.funName!.indexOf('Successful') < 0) {
278            if (data.frame!.width < 10) {
279              FuncStruct.drawTaskPoolUnSuccessFlag(ctx, data.frame!.x, (data.depth! + 0.5) * 18, 3, data!);
280            } else {
281              FuncStruct.drawTaskPoolUnSuccessFlag(ctx, data.frame!.x, (data.depth! + 0.5) * 18, 6, data!);
282            }
283          }
284          if (data.funName!.indexOf('H:Thread Timeout Exit') >= 0) {
285            FuncStruct.drawTaskPoolTimeOutFlag(ctx, data.frame!.x, (data.depth! + 0.5) * 18, 10, data!);
286          }
287        }
288        // 如果该函数没有结束时间,则绘制锯齿。
289        if (data.nofinish && data.frame!.width > 4) {
290          FuncStruct.drawRupture(ctx, data.frame.x, data.frame.y, data.frame.width, data.frame.height);
291        }
292      }
293    }
294  }
295
296  /**
297   * 绘制锯齿
298   * @param ctx 绘图上下文环境
299   * @param x 水平坐标
300   * @param y 垂直坐标
301   * @param width 函数矩形框的宽度
302   * @param height 函数矩形框的高度
303   */
304  static drawRupture(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number): void {
305    ctx.fillStyle = '#fff'; // 白色: '#fff' , 红色: '#FF0000';
306    let ruptureWidth = 5;
307    let ruptureNode = height / ruptureWidth;
308    let len = height / ruptureNode;
309    ctx.moveTo(x + width - 1, y);
310    for (let i = 1; i <= ruptureNode; i++) {
311      ctx.lineTo(x + width - 1 - (i % 2 === 0 ? 0 : ruptureWidth), y + len * i - 2);
312    }
313    ctx.closePath();
314    ctx.fill();
315  }
316
317  static drawTaskPoolUnSuccessFlag(
318    ctx: CanvasRenderingContext2D,
319    x: number,
320    y: number,
321    radius: number,
322    data: FuncStruct
323  ): void {
324    ctx.strokeStyle = '#FFC880';
325    ctx.lineWidth = 1;
326    ctx.beginPath();
327    ctx.arc(x + data.frame!.width, y, radius, 0, Math.PI * 2);
328    ctx.closePath();
329    ctx.fillStyle = '#E64566';
330    ctx.fill();
331    ctx.stroke();
332  }
333
334  static drawTaskPoolTimeOutFlag(
335    canvas: CanvasRenderingContext2D,
336    x: number,
337    y: number,
338    radius: number,
339    data: FuncStruct
340  ): void {
341    canvas.strokeStyle = '#FFC880';
342    canvas.lineWidth = 1;
343    canvas.beginPath();
344    canvas.arc(x + data.frame!.width + 20, y, radius, 0, Math.PI * 2);
345    canvas.closePath();
346    canvas.fillStyle = '#FFC880';
347    canvas.fill();
348    canvas.stroke();
349    canvas.font = '18px Arial';
350    canvas.fillStyle = ColorUtils.GREY_COLOR;
351    canvas.textAlign = 'center';
352    canvas.fillText('¡', x + data.frame!.width + 20, y);
353  }
354
355  static isSelected(data: FuncStruct): boolean {
356    return (
357      FuncStruct.selectFuncStruct !== undefined &&
358      FuncStruct.selectFuncStruct.startTs === data.startTs &&
359      FuncStruct.selectFuncStruct.depth === data.depth
360    );
361  }
362}
363