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 {
17  BaseStruct,
18  Rect,
19  Render,
20  drawString,
21  isFrameContainPoint,
22  ns2x,
23  drawLoadingFrame,
24} from './ProcedureWorkerCommon';
25import { TraceRow } from '../../component/trace/base/TraceRow';
26import { ColorUtils } from '../../component/trace/base/ColorUtils';
27import { type JsCpuProfilerChartFrame } from '../../bean/JsStruct';
28import { SpSystemTrace } from '../../component/SpSystemTrace';
29
30export class JsCpuProfilerRender extends Render {
31  renderMainThread(
32    req: {
33      useCache: boolean;
34      context: CanvasRenderingContext2D;
35      type: string;
36    },
37    jsCpuProfilerRow: TraceRow<JsCpuProfilerStruct>
38  ): void {
39    let filter = jsCpuProfilerRow.dataListCache;
40    jsCpuProfiler(
41      filter,
42      TraceRow.range!.startNS,
43      TraceRow.range!.endNS,
44      TraceRow.range!.totalNS, // @ts-ignore
45      jsCpuProfilerRow.frame,
46      req.useCache || !TraceRow.range!.refresh
47    );
48    drawLoadingFrame(req.context, filter, jsCpuProfilerRow);
49    req.context.beginPath();
50    let jsCpuProfilerFind = false;
51    for (let re of filter) {
52      JsCpuProfilerStruct.draw(req.context, re);
53      setHoveStruct(jsCpuProfilerRow, re, jsCpuProfilerFind);
54    }
55    if (!jsCpuProfilerFind && jsCpuProfilerRow.isHover) {
56      JsCpuProfilerStruct.hoverJsCpuProfilerStruct = undefined;
57    }
58    req.context.closePath();
59  }
60}
61function setHoveStruct(
62  jsCpuProfilerRow: TraceRow<JsCpuProfilerStruct>,
63  re: JsCpuProfilerStruct,
64  jsCpuProfilerFind: boolean
65): void {
66  if (jsCpuProfilerRow.isHover) {
67    if (
68      re.endTime - re.startTime === 0 ||
69      re.endTime - re.startTime === null ||
70      re.endTime - re.startTime === undefined
71    ) {
72      if (
73        re.frame &&
74        jsCpuProfilerRow.hoverX >= re.frame.x - 5 &&
75        jsCpuProfilerRow.hoverX <= re.frame.x + 5 &&
76        jsCpuProfilerRow.hoverY >= re.frame.y &&
77        jsCpuProfilerRow.hoverY <= re.frame.y + re.frame.height
78      ) {
79        JsCpuProfilerStruct.hoverJsCpuProfilerStruct = re;
80        jsCpuProfilerFind = true;
81      }
82    } else {
83      if (re.frame && isFrameContainPoint(re.frame, jsCpuProfilerRow.hoverX, jsCpuProfilerRow.hoverY)) {
84        JsCpuProfilerStruct.hoverJsCpuProfilerStruct = re;
85        jsCpuProfilerFind = true;
86      }
87    }
88  }
89}
90export function jsCpuProfiler(
91  filter: Array<JsCpuProfilerStruct>,
92  startNS: number,
93  endNS: number,
94  totalNS: number,
95  frame: Rect,
96  use: boolean
97): void {
98  if (use && filter.length > 0) {
99    for (let i = 0, len = filter.length; i < len; i++) {
100      if ((filter[i].startTime || 0) + (filter[i].totalTime || 0) >= startNS && (filter[i].startTime || 0) <= endNS) {
101        JsCpuProfilerStruct.setJsCpuProfilerFrame(filter[i], startNS, endNS, totalNS, frame);
102      } else {
103        filter[i].frame = undefined;
104      }
105    }
106  }
107}
108
109const padding = 1;
110export function JsCpuProfilerStructOnClick(
111  clickRowType: string,
112  sp: SpSystemTrace,
113  row: TraceRow<JsCpuProfilerStruct>,
114  entry?: JsCpuProfilerStruct
115): Promise<unknown> {
116  return new Promise((resolve, reject) => {
117    if (clickRowType === TraceRow.ROW_TYPE_JS_CPU_PROFILER) {
118      if (row.findHoverStruct) {
119        row.findHoverStruct();
120      } else {
121        JsCpuProfilerStruct.hoverJsCpuProfilerStruct =
122          JsCpuProfilerStruct.hoverJsCpuProfilerStruct || row.getHoverStruct();
123      }
124    }
125    if (clickRowType === TraceRow.ROW_TYPE_JS_CPU_PROFILER && (JsCpuProfilerStruct.hoverJsCpuProfilerStruct || entry)) {
126      JsCpuProfilerStruct.selectJsCpuProfilerStruct = entry || JsCpuProfilerStruct.hoverJsCpuProfilerStruct;
127      let selectStruct = JsCpuProfilerStruct.selectJsCpuProfilerStruct;
128      let dataArr: Array<JsCpuProfilerChartFrame> = [];
129      let parentIdArr: Array<number> = [];
130      let that = sp;
131      getTopJsCpuProfilerStruct(selectStruct!.parentId, selectStruct!, that, dataArr, parentIdArr);
132      that.traceSheetEL?.displayJsProfilerData(dataArr);
133      reject(new Error());
134    } else {
135      resolve(null);
136    }
137  });
138}
139
140function getTopJsCpuProfilerStruct(
141  parentId: number,
142  selectStruct: JsCpuProfilerStruct,
143  that: SpSystemTrace,
144  dataArr: Array<JsCpuProfilerChartFrame> = [],
145  parentIdArr: Array<number> = []
146): void {
147  if (parentId === -1 && selectStruct.parentId === -1) {
148    // 点击的函数是第一层,直接设置其children的isSelect为true,不用重新算totalTime
149    let data = that.chartManager!.arkTsChart.chartFrameMap.get(selectStruct!.id);
150    if (data && dataArr.length === 0) {
151      let copyData = JSON.parse(JSON.stringify(data));
152      setSelectChildrenState(copyData);
153      dataArr.push(copyData);
154    }
155  } else {
156    let parent = that.chartManager!.arkTsChart.chartFrameMap.get(parentId);
157    if (parent) {
158      parentIdArr.push(parent.id);
159      getTopJsCpuProfilerStruct(parent.parentId!, selectStruct, that, dataArr, parentIdArr);
160      if (parent.parentId === -1 && dataArr.length === 0) {
161        let data = that.chartManager!.arkTsChart.chartFrameMap.get(parent.id);
162        let copyParent = JSON.parse(JSON.stringify(data));
163        copyParent.totalTime = selectStruct.totalTime;
164        copyParent.selfTime = 0;
165        // depth为0的isSelect改为true
166        copyParent.isSelect = true;
167        if (copyParent.children.length > 0) {
168          getSelectStruct(copyParent, selectStruct, parentIdArr);
169        }
170        dataArr.push(copyParent);
171      }
172    }
173  }
174}
175
176function getSelectStruct(
177  data: JsCpuProfilerChartFrame,
178  selectStruct: JsCpuProfilerStruct,
179  parentIdArr: number[]
180): void {
181  for (let child of data.children) {
182    if (child === null) {
183      continue;
184    }
185    if (child.id === selectStruct!.id) {
186      // 将点击的函数的children的isSelect改为true
187      setSelectChildrenState(child);
188    } else {
189      getSelectStruct(child, selectStruct, parentIdArr);
190    }
191    if (parentIdArr.includes(child.id)) {
192      child.isSelect = true;
193      child.totalTime = selectStruct.totalTime;
194      child.selfTime = 0;
195    }
196  }
197}
198
199function setSelectChildrenState(data: JsCpuProfilerChartFrame): void {
200  data.isSelect = true;
201  if (data.children.length > 0) {
202    for (let child of data.children) {
203      if (child === null) {
204        continue;
205      }
206      setSelectChildrenState(child);
207    }
208  }
209}
210
211export class JsCpuProfilerStruct extends BaseStruct {
212  static lastSelectJsCpuProfilerStruct: JsCpuProfilerStruct | undefined;
213  static selectJsCpuProfilerStruct: JsCpuProfilerStruct | undefined;
214  static hoverJsCpuProfilerStruct: JsCpuProfilerStruct | undefined;
215  id: number = 0;
216  name: string = '';
217  startTime: number = 0;
218  endTime: number = 0;
219  selfTime: number = 0;
220  totalTime: number = 0;
221  url: string = '';
222  depth: number = 0;
223  parentId: number = 0;
224  children!: Array<JsCpuProfilerChartFrame>;
225  isSelect: boolean = false;
226
227  static setJsCpuProfilerFrame(
228    jsCpuProfilerNode: JsCpuProfilerStruct,
229    startNS: number,
230    endNS: number,
231    totalNS: number,
232    frame: Rect
233  ): void {
234    let x1: number;
235    let x2: number;
236    if ((jsCpuProfilerNode.startTime || 0) > startNS && (jsCpuProfilerNode.startTime || 0) < endNS) {
237      x1 = ns2x(jsCpuProfilerNode.startTime || 0, startNS, endNS, totalNS, frame);
238    } else {
239      x1 = 0;
240    }
241    if (
242      (jsCpuProfilerNode.startTime || 0) + (jsCpuProfilerNode.totalTime || 0) > startNS &&
243      (jsCpuProfilerNode.startTime || 0) + (jsCpuProfilerNode.totalTime || 0) < endNS
244    ) {
245      x2 = ns2x(
246        (jsCpuProfilerNode.startTime || 0) + (jsCpuProfilerNode.totalTime || 0),
247        startNS,
248        endNS,
249        totalNS,
250        frame
251      );
252    } else {
253      x2 = frame.width;
254    }
255    if (!jsCpuProfilerNode.frame) {
256      jsCpuProfilerNode.frame = new Rect(0, 0, 0, 0);
257    }
258    let getV: number = x2 - x1 < 1 ? 1 : x2 - x1;
259    jsCpuProfilerNode.frame.x = Math.floor(x1);
260    jsCpuProfilerNode.frame.y = jsCpuProfilerNode.depth * 20;
261    jsCpuProfilerNode.frame.width = Math.ceil(getV);
262    jsCpuProfilerNode.frame.height = 20;
263  }
264
265  static draw(jsCpuProfilerCtx: CanvasRenderingContext2D, data: JsCpuProfilerStruct): void {
266    if (data.frame) {
267      if (data.endTime - data.startTime === undefined || data.endTime - data.startTime === null) {
268      } else {
269        jsCpuProfilerCtx.globalAlpha = 1;
270        if (data.name === '(program)') {
271          jsCpuProfilerCtx.fillStyle = '#ccc';
272        } else if (data.name === '(idle)') {
273          jsCpuProfilerCtx.fillStyle = '#f0f0f0';
274        } else {
275          jsCpuProfilerCtx.fillStyle =
276            ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', 0, ColorUtils.FUNC_COLOR.length)];
277        }
278        let miniHeight = 20;
279        if (JsCpuProfilerStruct.hoverJsCpuProfilerStruct && data === JsCpuProfilerStruct.hoverJsCpuProfilerStruct) {
280          jsCpuProfilerCtx.globalAlpha = 0.7;
281        }
282        jsCpuProfilerCtx.fillRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2);
283        if (data.frame.width > 8) {
284          jsCpuProfilerCtx.lineWidth = 1;
285          jsCpuProfilerCtx.fillStyle = ColorUtils.funcTextColor(
286            ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', 0, ColorUtils.FUNC_COLOR.length)]
287          );
288          jsCpuProfilerCtx.textBaseline = 'middle';
289          drawString(jsCpuProfilerCtx, `${data.name || ''}`, 4, data.frame, data);
290        }
291        if (
292          JsCpuProfilerStruct.selectJsCpuProfilerStruct &&
293          JsCpuProfilerStruct.equals(JsCpuProfilerStruct.selectJsCpuProfilerStruct, data)
294        ) {
295          jsCpuProfilerCtx.strokeStyle = '#000';
296          jsCpuProfilerCtx.lineWidth = 2;
297          jsCpuProfilerCtx.strokeRect(
298            data.frame.x + 1,
299            data.frame.y + 1,
300            data.frame.width - 2,
301            miniHeight - padding * 2 - 2
302          );
303        }
304      }
305    }
306  }
307  static equals(d1: JsCpuProfilerStruct, d2: JsCpuProfilerStruct): boolean {
308    return (
309      d1 &&
310      d2 &&
311      d1.id === d2.id &&
312      d1.name === d2.name &&
313      d1.url === d2.url &&
314      d1.depth === d2.depth &&
315      d1.totalTime === d2.totalTime &&
316      d1.selfTime === d2.selfTime &&
317      d1.startTime === d2.startTime &&
318      d1.endTime === d2.endTime
319    );
320  }
321}
322