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 {
18  BaseStruct,
19  dataFilterHandler,
20  drawFlagLine,
21  drawLines,
22  drawLoadingFrame,
23  drawSelection,
24  drawWakeUp,
25  drawWakeUpList,
26  Rect,
27  Render,
28  RequestMessage,
29} from '../ProcedureWorkerCommon';
30import { TraceRow } from '../../../component/trace/base/TraceRow';
31import { SpSystemTrace } from '../../../component/SpSystemTrace';
32import { Utils } from '../../../component/trace/base/Utils';
33
34export class EmptyRender extends Render {
35  //@ts-ignore
36  renderMainThread(req: unknown, row: TraceRow<unknown>): void {
37    //@ts-ignore
38    drawLoadingFrame(req.context, [], row);
39  }
40  render(cpuReqMessage: RequestMessage, list: Array<CpuStruct>, filter: Array<CpuStruct>): void {
41    if (cpuReqMessage.canvas) {
42      cpuReqMessage.context.clearRect(0, 0, cpuReqMessage.frame.width, cpuReqMessage.frame.height);
43      cpuReqMessage.context.beginPath();
44      drawLines(cpuReqMessage.context, cpuReqMessage.xs!, cpuReqMessage.frame.height, cpuReqMessage.lineColor);
45      drawSelection(cpuReqMessage.context, cpuReqMessage.params);
46      cpuReqMessage.context.closePath();
47      drawFlagLine(
48        cpuReqMessage.context,
49        cpuReqMessage.flagMoveInfo!,
50        cpuReqMessage.flagSelectedInfo!,
51        cpuReqMessage.startNS,
52        cpuReqMessage.endNS,
53        cpuReqMessage.totalNS,
54        cpuReqMessage.frame,
55        cpuReqMessage.slicesTime
56      );
57    }
58    // @ts-ignore
59    self.postMessage({
60      id: cpuReqMessage.id,
61      type: cpuReqMessage.type,
62      results: cpuReqMessage.canvas ? undefined : filter,
63      hover: null,
64    });
65  }
66}
67
68export class CpuRender {
69  renderMainThread(
70    req: {
71      ctx: CanvasRenderingContext2D;
72      useCache: boolean;
73      type: string;
74      translateY: number;
75    },
76    row: TraceRow<CpuStruct>
77  ): void {
78    let cpuList = row.dataList;
79    let cpuFilter = row.dataListCache;
80    let startNS = TraceRow.range!.startNS ?? 0;
81    let endNS = TraceRow.range!.endNS ?? 0;
82    let totalNS = TraceRow.range!.totalNS ?? 0;
83    dataFilterHandler(cpuList, cpuFilter, {
84      startKey: 'startTime',
85      durKey: 'dur',
86      startNS: startNS,
87      endNS: endNS,
88      totalNS: totalNS,
89      frame: row.frame,
90      paddingTop: 3,
91      useCache: req.useCache || !(TraceRow.range?.refresh ?? false),
92    });
93    drawLoadingFrame(req.ctx, cpuFilter, row);
94    req.ctx.beginPath();
95    req.ctx.font = '11px sans-serif';
96    cpuFilter.forEach((re) => {
97      re.translateY = req.translateY;
98      CpuStruct.draw(req.ctx, re, req.translateY);
99    });
100    req.ctx.closePath();
101    if ((row.traceId === Utils.currentSelectTrace) || (row.traceId === null && Utils.currentSelectTrace === undefined)) {
102      let currentCpu = parseInt(req.type!.replace('cpu-data-', ''));
103      let wakeup = req.type === `cpu-data-${CpuStruct.selectCpuStruct?.cpu || 0}` ?
104        CpuStruct.selectCpuStruct : undefined;
105      drawWakeUp(req.ctx, CpuStruct.wakeupBean, startNS, endNS, totalNS, row.frame, wakeup, currentCpu, true);
106      for (let i = 0; i < SpSystemTrace.wakeupList.length; i++) {
107        if (i + 1 === SpSystemTrace.wakeupList.length) {
108          return;
109        }
110        let wake = SpSystemTrace.wakeupList[i + 1];
111        let wakeupListItem =
112          req.type === `cpu-data-${SpSystemTrace.wakeupList[i]?.cpu || 0}` ? SpSystemTrace.wakeupList[i] : undefined;
113        drawWakeUpList(req.ctx, wake, startNS, endNS, totalNS, row.frame, wakeupListItem, currentCpu, true);
114      }
115    }
116  }
117
118  cpu(
119    cpuList: Array<CpuStruct>,
120    cpuRes: Array<CpuStruct>,
121    startNS: number,
122    endNS: number,
123    totalNS: number,
124    frame: Rect,
125    use: boolean
126  ): void {
127    if (use && cpuRes.length > 0) {
128      this.setFrameCpuByRes(cpuRes, startNS, endNS, frame);
129      return;
130    }
131    if (cpuList) {
132      this.setFrameCpuByList(cpuRes, startNS, endNS, frame, cpuList);
133    }
134  }
135
136  setFrameCpuByRes(cpuRes: Array<CpuStruct>, startNS: number, endNS: number, frame: Rect): void {
137    let pns = (endNS - startNS) / frame.width;
138    let y = frame.y + 5;
139    let height = frame.height - 10;
140    for (let i = 0, len = cpuRes.length; i < len; i++) {
141      let it = cpuRes[i];
142      if ((it.startTime || 0) + (it.dur || 0) > startNS && (it.startTime || 0) < endNS) {
143        if (!cpuRes[i].frame) {
144          cpuRes[i].frame = new Rect(0, 0, 0, 0);
145          cpuRes[i].frame!.y = y;
146          cpuRes[i].frame!.height = height;
147        }
148        CpuStruct.setCpuFrame(cpuRes[i], pns, startNS, endNS, frame);
149      } else {
150        cpuRes[i].frame = undefined;
151      }
152    }
153  }
154
155  setFrameCpuByList( cpuRes: Array<CpuStruct>, startNS: number, endNS: number, frame: Rect,
156    cpuList: Array<CpuStruct>): void {
157    cpuRes.length = 0;
158    let pns = (endNS - startNS) / frame.width; //每个像素多少ns
159    let y = frame.y + 5;
160    let height = frame.height - 10;
161    let left = 0;
162    let right = 0;
163    for (let i = 0, j = cpuList.length - 1, ib = true, jb = true; i < cpuList.length, j >= 0; i++, j--) {
164      if (cpuList[j].startTime! <= endNS && jb) {
165        right = j;
166        jb = false;
167      }
168      if (cpuList[i].startTime! + cpuList[i].dur! >= startNS && ib) {
169        left = i;
170        ib = false;
171      }
172      if (!ib && !jb) {
173        break;
174      }
175    }
176    let slice = cpuList.slice(left, right + 1);
177    let sum = 0;
178    for (let i = 0; i < slice.length; i++) {
179      if (!slice[i].frame) {
180        slice[i].frame = new Rect(0, 0, 0, 0);
181        slice[i].frame!.y = y;
182        slice[i].frame!.height = height;
183      }
184      if (slice[i].dur! >= pns) {
185        slice[i].v = true;
186        CpuStruct.setCpuFrame(slice[i], pns, startNS, endNS, frame);
187      } else {
188        if (i > 0) {
189          let c = slice[i].startTime! - slice[i - 1].startTime! - slice[i - 1].dur!;
190          if (c < pns && sum < pns) {
191            sum += c + slice[i - 1].dur!;
192            slice[i].v = false;
193          } else {
194            slice[i].v = true;
195            CpuStruct.setCpuFrame(slice[i], pns, startNS, endNS, frame);
196            sum = 0;
197          }
198        }
199      }
200    }
201    cpuRes.push(...slice.filter((it) => it.v));
202  }
203}
204export function CpuStructOnClick(
205  rowType: string,
206  sp: SpSystemTrace,
207  cpuClickHandler: unknown,
208  entry?: CpuStruct,
209): Promise<unknown> {
210  return new Promise((resolve, reject) => {
211    if (rowType === TraceRow.ROW_TYPE_CPU && (CpuStruct.hoverCpuStruct || entry)) {
212      CpuStruct.selectCpuStruct = entry || CpuStruct.hoverCpuStruct;
213      sp.timerShaftEL?.drawTriangle(CpuStruct.selectCpuStruct!.startTime || 0, 'inverted');
214      sp.traceSheetEL?.displayCpuData(
215        CpuStruct.selectCpuStruct!,
216        (wakeUpBean) => {
217          CpuStruct.wakeupBean = wakeUpBean;
218          sp.refreshCanvas(false);
219        },
220        //@ts-ignore
221        cpuClickHandler
222      );
223      sp.timerShaftEL?.modifyFlagList(undefined);
224      reject(new Error());
225    } else {
226      resolve(null);
227    }
228  });
229}
230export class CpuStruct extends BaseStruct {
231  static hoverCpuStruct: CpuStruct | undefined;
232  static selectCpuStruct: CpuStruct | undefined;
233  static wakeupBean: WakeupBean | null | undefined = null;
234  cpu: number | undefined;
235  dur: number | undefined;
236  end_state: string | undefined;
237  state: string | undefined;
238  id: number | undefined;
239  tid: number | undefined;
240  name: string | undefined;
241  priority: number | undefined;
242  processCmdLine: string | undefined;
243  processId: number | undefined;
244  processName: string | undefined;
245  displayProcess: string | undefined;
246  displayThread: string | undefined;
247  measurePWidth: number = 0;
248  measureTWidth: number = 0;
249  startTime: number | undefined;
250  argSetID: number | undefined;
251  type: string | undefined;
252  v: boolean = false;
253  nofinish: boolean = false;
254  ts: number | undefined;
255  itid: number | undefined;
256  process: string | undefined;
257  pid: number | undefined;
258  thread: string | undefined;
259  isKeyPath?: number;
260
261  static draw(ctx: CanvasRenderingContext2D, data: CpuStruct, translateY: number): void {
262    if (data.frame) {
263      let pid = data.processId || 0;
264      let tid = data.tid || 0;
265      let width = data.frame.width || 0;
266      if (data.tid === CpuStruct.hoverCpuStruct?.tid || !CpuStruct.hoverCpuStruct) {
267        ctx.globalAlpha = 1;
268        ctx.fillStyle = ColorUtils.colorForTid(pid > 0 ? pid : tid);
269      } else if (data.processId === CpuStruct.hoverCpuStruct?.processId) {
270        ctx.globalAlpha = 0.6;
271        ctx.fillStyle = ColorUtils.colorForTid(pid > 0 ? pid : tid);
272      } else {
273        ctx.globalAlpha = 1;
274        ctx.fillStyle = '#e0e0e0';
275      }
276      ctx.fillRect(data.frame.x, data.frame.y, width, data.frame.height);
277      ctx.globalAlpha = 1;
278      CpuStruct.drawText(ctx, data, width, pid, tid);
279      CpuStruct.drawRim(ctx, data, width);
280    }
281  }
282  static drawText(ctx: CanvasRenderingContext2D, data: CpuStruct, width: number, pid: number, tid: number): void {
283    let textFillWidth = width - textPadding * 2;
284    if (data.frame && textFillWidth > 3) {
285      if (data.displayProcess === undefined) {
286        data.displayProcess = `${data.processName || 'Process'} [${data.processId}]`;
287        data.measurePWidth = ctx.measureText(data.displayProcess).width;
288      }
289      if (data.displayThread === undefined) {
290        data.displayThread = `${data.name || 'Thread'} [${data.tid}] [Prio:${data.priority || 0}]`;
291        data.measureTWidth = ctx.measureText(data.displayThread).width;
292      }
293      let processCharWidth = Math.round(data.measurePWidth / data.displayProcess.length);
294      let threadCharWidth = Math.round(data.measureTWidth / data.displayThread.length);
295      ctx.fillStyle = ColorUtils.funcTextColor(ColorUtils.colorForTid(pid > 0 ? pid : tid));
296      ctx.font = '9px sans-serif';
297      ctx.textBaseline = 'bottom';
298      let y = data.frame.height / 2 + data.frame.y;
299      if (data.measurePWidth < textFillWidth) {
300        let x1 = Math.floor(width / 2 - data.measurePWidth / 2 + data.frame.x + textPadding);
301        ctx.fillText(data.displayProcess, x1, y, textFillWidth);
302      } else {
303        if (textFillWidth >= processCharWidth) {
304          let chatNum = textFillWidth / processCharWidth;
305          let x1 = data.frame.x + textPadding;
306          if (chatNum < 2) {
307            ctx.fillText(data.displayProcess.substring(0, 1), x1, y, textFillWidth);
308          } else {
309            ctx.fillText(`${data.displayProcess.substring(0, chatNum - 1)}...`, x1, y, textFillWidth);
310          }
311        }
312      }
313      ctx.textBaseline = 'top';
314      if (data.measureTWidth < textFillWidth) {
315        let x2 = Math.floor(width / 2 - data.measureTWidth / 2 + data.frame.x + textPadding);
316        ctx.fillText(data.displayThread, x2, y + 2, textFillWidth);
317      } else {
318        if (textFillWidth >= threadCharWidth) {
319          let chatNum = textFillWidth / threadCharWidth;
320          let x1 = data.frame.x + textPadding;
321          if (chatNum < 2) {
322            ctx.fillText(data.displayThread.substring(0, 1), x1, y + 2, textFillWidth);
323          } else {
324            ctx.fillText( `${data.displayThread.substring(0, chatNum - 1)}...`, x1, y + 2, textFillWidth);
325          }
326        }
327      }
328    }
329  }
330  static drawRim(ctx: CanvasRenderingContext2D, data: CpuStruct, width: number): void {
331    if (data.frame) {
332      if (data.nofinish && width > 4) {
333        ctx.fillStyle = '#fff';
334        let ruptureWidth = 4;
335        let ruptureNode = 8;
336        ctx.moveTo(data.frame.x + data.frame.width - 1, data.frame.y);
337        for (let i = 1; i <= ruptureNode; i++) {
338          ctx.lineTo(
339            data.frame.x + data.frame.width - 1 - (i % 2 === 0 ? 0 : ruptureWidth),
340            data.frame.y + (data.frame.height / ruptureNode) * i
341          );
342        }
343        ctx.closePath();
344        ctx.fill();
345      }
346      if (data.isKeyPath) {
347        const gradient = ctx.createLinearGradient(0, 0, 0, 40);
348        gradient.addColorStop(0, '#000000');
349        gradient.addColorStop(0.5, '#0000FF');
350        gradient.addColorStop(1, '#FF0000');
351        ctx.strokeStyle = gradient;
352        ctx.lineWidth = 4;
353        ctx.strokeRect(data.frame.x, data.frame.y - 2, width - 2, data.frame.height + 2);
354      }
355      if (CpuStruct.selectCpuStruct && CpuStruct.equals(CpuStruct.selectCpuStruct, data)) {
356        ctx.strokeStyle = '#232c5d';
357        ctx.lineWidth = 2;
358        ctx.strokeRect(data.frame.x, data.frame.y, width - 2, data.frame.height);
359      }
360    }
361  }
362
363  static setCpuFrame(cpuNode: CpuStruct, pns: number, startNS: number, endNS: number, frame: Rect): void {
364    if (!cpuNode.frame) {
365      return;
366    }
367    if ((cpuNode.startTime || 0) < startNS) {
368      cpuNode.frame.x = 0;
369    } else {
370      cpuNode.frame.x = Math.floor(((cpuNode.startTime || 0) - startNS) / pns);
371    }
372    if ((cpuNode.startTime || 0) + (cpuNode.dur || 0) > endNS) {
373      cpuNode.frame.width = frame.width - cpuNode.frame.x;
374    } else {
375      cpuNode.frame.width = Math.ceil(
376        ((cpuNode.startTime || 0) + (cpuNode.dur || 0) - startNS) / pns - cpuNode.frame.x
377      );
378    }
379    if (cpuNode.frame.width < 1) {
380      cpuNode.frame.width = 1;
381    }
382  }
383
384  static equals(d1: CpuStruct, d2: CpuStruct): boolean {
385    return (
386      d1 &&
387      d2 &&
388      d1.cpu === d2.cpu &&
389      d1.tid === d2.tid &&
390      d1.processId === d2.processId &&
391      d1.startTime === d2.startTime &&
392      d1.dur === d2.dur
393    );
394  }
395}
396
397export class WakeupBean {
398  wakeupTime: number | undefined;
399  cpu: number | undefined;
400  process: string | undefined;
401  pid: number | undefined;
402  thread: string | undefined;
403  dur: number | null | undefined;
404  tid: number | undefined;
405  schedulingLatency: number | undefined;
406  ts: number | undefined;
407  schedulingDesc: string | undefined;
408  itid: number | undefined;
409  state: string | undefined;
410  argSetID: number | undefined;
411}
412
413const textPadding = 2;
414