• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 { CpuStruct, WakeupBean } from './ProcedureWorkerCPU.js';
17import { TraceRow } from '../../component/trace/base/TraceRow.js';
18import { TimerShaftElement } from '../../component/trace/TimerShaftElement';
19
20export abstract class Render {
21  abstract renderMainThread(req: any, row: TraceRow<any>): void;
22}
23
24export abstract class PerfRender {
25  abstract render(req: RequestMessage, list: Array<any>, filter: Array<any>, dataList2: Array<any>): void;
26}
27
28export class RequestMessage {
29  type: string | undefined | null;
30  lazyRefresh: boolean | undefined;
31  intervalPerf: any;
32  canvas: any;
33  context: any;
34  params: any;
35  online: any;
36  buf: any;
37  isRangeSelect: any;
38  isHover: any;
39  xs: any;
40  frame: any;
41  flagMoveInfo: any;
42  flagSelectedInfo: any;
43  hoverX: any;
44  hoverY: any;
45  startNS: any;
46  endNS: any;
47  totalNS: any;
48  slicesTime:
49    | {
50        startTime: number | null;
51        endTime: number | null;
52        color: string | null;
53      }
54    | undefined;
55  range: any;
56  scale: any;
57  chartColor: any;
58  canvasWidth: any;
59  canvasHeight: any;
60  useCache: any;
61  lineColor: any;
62  wakeupBean: WakeupBean | undefined | null;
63  id: any;
64  postMessage:
65    | {
66        (message: any, targetOrigin: string, transfer?: Transferable[]): void;
67        (message: any, options?: WindowPostMessageOptions): void;
68      }
69    | undefined;
70}
71
72export function ns2s(ns: number): string {
73  let second1 = 1_000_000_000; // 1 second
74  let millisecond = 1_000_000; // 1 millisecond
75  let microsecond = 1_000; // 1 microsecond
76  let res;
77  if (ns >= second1) {
78    res = (ns / 1000 / 1000 / 1000).toFixed(1) + ' s';
79  } else if (ns >= millisecond) {
80    res = (ns / 1000 / 1000).toFixed(1) + ' ms';
81  } else if (ns >= microsecond) {
82    res = (ns / 1000).toFixed(1) + ' μs';
83  } else if (ns > 0) {
84    res = ns.toFixed(1) + ' ns';
85  } else {
86    res = ns.toFixed(1) + ' s';
87  }
88  return res;
89}
90
91export function isFrameContainPoint(frame: Rect, x: number, y: number): boolean {
92  return x >= frame.x && x <= frame.x + frame.width && y >= frame.y && y <= frame.y + frame.height;
93}
94
95class FilterConfig {
96  startNS: number = 0;
97  endNS: number = 0;
98  totalNS: number = 0;
99  frame: any = null;
100  useCache: boolean = false;
101  startKey: string = 'startNS';
102  durKey: string = 'dur';
103  paddingTop: number = 0;
104}
105
106export function fillCacheData(filterData: Array<any>, condition: FilterConfig): boolean {
107  if (condition.useCache && filterData.length > 0) {
108    let pns = (condition.endNS - condition.startNS) / condition.frame.width;
109    let y = condition.frame.y + condition.paddingTop;
110    let height = condition.frame.height - condition.paddingTop * 2;
111    for (let i = 0, len = filterData.length; i < len; i++) {
112      let it = filterData[i];
113      if (
114        (it[condition.startKey] || 0) + (it[condition.durKey] || 0) > condition.startNS &&
115        (it[condition.startKey] || 0) < condition.endNS
116      ) {
117        if (!filterData[i].frame) {
118          filterData[i].frame = {};
119          filterData[i].frame.y = y;
120          filterData[i].frame.height = height;
121        }
122        setNodeFrame(
123          filterData[i],
124          pns,
125          condition.startNS,
126          condition.endNS,
127          condition.frame,
128          condition.startKey,
129          condition.durKey
130        );
131      } else {
132        filterData[i].frame = null;
133      }
134    }
135    return true;
136  }
137  return false;
138}
139
140export function findRange(fullData: Array<any>, condition: FilterConfig): Array<any> {
141  let left = 0,
142    right = 0;
143  for (let i = 0, j = fullData.length - 1, ib = true, jb = true; i < fullData.length, j >= 0; i++, j--) {
144    if (fullData[j][condition.startKey] <= condition.endNS && jb) {
145      right = j;
146      jb = false;
147    }
148    if (fullData[i][condition.startKey] + fullData[i][condition.durKey] >= condition.startNS && ib) {
149      left = i;
150      ib = false;
151    }
152    if (!ib && !jb) {
153      break;
154    }
155  }
156  let slice = fullData.slice(left, right + 1);
157  return slice;
158}
159
160export function dataFilterHandler(fullData: Array<any>, filterData: Array<any>, condition: FilterConfig) {
161  if (fillCacheData(filterData, condition)) return;
162  if (fullData) {
163    filterData.length = 0;
164    let pns = (condition.endNS - condition.startNS) / condition.frame.width; //每个像素多少ns
165    let y = condition.frame.y + condition.paddingTop;
166    let height = condition.frame.height - condition.paddingTop * 2;
167    let slice = findRange(fullData, condition);
168    let sum = 0;
169    for (let i = 0; i < slice.length; i++) {
170      if (!slice[i].frame) {
171        slice[i].frame = {};
172        slice[i].frame.y = y;
173        slice[i].frame.height = height;
174      }
175      if (i === slice.length - 1) {
176        if (slice[i][condition.durKey] === undefined || slice[i][condition.durKey] === null) {
177          slice[i][condition.durKey] = (condition.endNS || 0) - (slice[i][condition.startKey] || 0);
178        }
179      } else {
180        if (slice[i][condition.durKey] === undefined || slice[i][condition.durKey] === null) {
181          slice[i][condition.durKey] = (slice[i + 1][condition.startKey] || 0) - (slice[i][condition.startKey] || 0);
182        }
183      }
184      if (slice[i][condition.durKey] >= pns || slice.length < 100) {
185        slice[i].v = true;
186        setNodeFrame(
187          slice[i],
188          pns,
189          condition.startNS,
190          condition.endNS,
191          condition.frame,
192          condition.startKey,
193          condition.durKey
194        );
195      } else {
196        if (i > 0) {
197          let c = slice[i][condition.startKey] - slice[i - 1][condition.startKey] - slice[i - 1][condition.durKey];
198          if (c < pns && sum < pns) {
199            sum += c + slice[i - 1][condition.durKey];
200            slice[i].v = false;
201          } else {
202            slice[i].v = true;
203            setNodeFrame(
204              slice[i],
205              pns,
206              condition.startNS,
207              condition.endNS,
208              condition.frame,
209              condition.startKey,
210              condition.durKey
211            );
212            sum = 0;
213          }
214        }
215      }
216    }
217    filterData.push(...slice.filter((it) => it.v));
218  }
219}
220
221function setNodeFrame(
222  node: any,
223  pns: number,
224  startNS: number,
225  endNS: number,
226  frame: any,
227  startKey: string,
228  durKey: string
229) {
230  if ((node[startKey] || 0) < startNS) {
231    node.frame.x = 0;
232  } else {
233    node.frame.x = Math.floor(((node[startKey] || 0) - startNS) / pns);
234  }
235  if ((node[startKey] || 0) + (node[durKey] || 0) > endNS) {
236    node.frame.width = frame.width - node.frame.x;
237  } else {
238    node.frame.width = Math.ceil(((node[startKey] || 0) + (node[durKey] || 0) - startNS) / pns - node.frame.x);
239  }
240  if (node.frame.width < 1) {
241    node.frame.width = 1;
242  }
243}
244
245export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: any) {
246  // @ts-ignore
247  if (endNS == 0) {
248    endNS = duration;
249  }
250  let xSize: number = ((ns - startNS) * rect.width) / (endNS - startNS);
251  if (xSize < 0) {
252    xSize = 0;
253  } else if (xSize > rect.width) {
254    xSize = rect.width;
255  }
256  return xSize;
257}
258
259export function ns2xByTimeShaft(ns: number, tse: TimerShaftElement) {
260  let startNS = tse.getRange()!.startNS;
261  let endNS = tse.getRange()!.endNS;
262  let duration = tse.getRange()!.totalNS;
263  if (endNS == 0) {
264    endNS = duration;
265  }
266  let width = tse.getBoundingClientRect().width - 258;
267  let xSize: number = ((ns - startNS) * width) / (endNS - startNS);
268  if (xSize < 0) {
269    xSize = 0;
270  } else if (xSize > width) {
271    xSize = width;
272  }
273  return xSize;
274}
275
276export class Rect {
277  x: number = 0;
278  y: number = 0;
279  width: number = 0;
280  height: number = 0;
281  constructor(x: number, y: number, width: number, height: number) {
282    this.x = x;
283    this.y = y;
284    this.width = width;
285    this.height = height;
286  }
287
288  static intersect(r1: Rect, rect: Rect): boolean {
289    let minX = r1.x <= rect.x ? r1.x : rect.x;
290    let minY = r1.y <= rect.y ? r1.y : rect.y;
291    let maxX = r1.x + r1.width >= rect.x + rect.width ? r1.x + r1.width : rect.x + rect.width;
292    let maxY = r1.y + r1.height >= rect.y + rect.height ? r1.y + r1.height : rect.y + rect.height;
293    if (maxX - minX <= rect.width + r1.width && maxY - minY <= r1.height + rect.height) {
294      return true;
295    } else {
296      return false;
297    }
298  }
299
300  static contains(rect: Rect, x: number, y: number): boolean {
301    return rect.x <= x && x <= rect.x + rect.width && rect.y <= y && y <= rect.y + rect.height;
302  }
303
304  static containsWithMargin(rect: Rect, x: number, y: number, t: number, r: number, b: number, l: number): boolean {
305    return rect.x - l <= x && x <= rect.x + rect.width + r && rect.y - t <= y && y <= rect.y + rect.height + b;
306  }
307
308  static containsWithPadding(
309    rect: Rect,
310    x: number,
311    y: number,
312    paddingLeftRight: number,
313    paddingTopBottom: number
314  ): boolean {
315    return (
316      rect.x + paddingLeftRight <= x &&
317      rect.y + paddingTopBottom <= y &&
318      x <= rect.x + rect.width - paddingLeftRight &&
319      y <= rect.y + rect.height - paddingTopBottom
320    );
321  }
322
323  /**
324   * 判断是否相交
325   * @param rect
326   */
327  intersect(rect: Rect): boolean {
328    let minX = this.x <= rect.x ? this.x : rect.x;
329    let minY = this.y <= rect.y ? this.y : rect.y;
330    let maxX = this.x + this.width >= rect.x + rect.width ? this.x + this.width : rect.x + rect.width;
331    let maxY = this.y + this.height >= rect.y + rect.height ? this.y + this.height : rect.y + rect.height;
332    if (maxX - minX <= rect.width + this.width && maxY - minY <= this.height + rect.height) {
333      return true;
334    } else {
335      return false;
336    }
337  }
338
339  contains(x: number, y: number): boolean {
340    return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height;
341  }
342
343  containsWithMargin(x: number, y: number, t: number, r: number, b: number, l: number): boolean {
344    return this.x - l <= x && x <= this.x + this.width + r && this.y - t <= y && y <= this.y + this.height + b;
345  }
346
347  containsWithPadding(x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean {
348    return (
349      this.x + paddingLeftRight <= x &&
350      x <= this.x + this.width - paddingLeftRight &&
351      this.y + paddingTopBottom <= y &&
352      y <= this.y + this.height - paddingTopBottom
353    );
354  }
355}
356
357export class Point {
358  x: number = 0;
359  y: number = 0;
360  isRight: boolean = true;
361
362  constructor(x: number, y: number, isRight: boolean = true) {
363    this.x = x;
364    this.y = y;
365    this.isRight = isRight;
366  }
367}
368
369export enum LineType {
370  brokenLine,
371  bezierCurve,
372}
373
374export class PairPoint {
375  x: number = 0;
376  ns: number = 0;
377  y: number = 0;
378  offsetY: number = 0;
379  rowEL: TraceRow<any>;
380  isRight: boolean = true;
381  lineType?: LineType;
382  business: string = '';
383  hidden?: boolean = false;
384  constructor(
385    rowEL: TraceRow<any>,
386    x: number,
387    y: number,
388    ns: number,
389    offsetY: number,
390    isRight: boolean,
391    business: string
392  ) {
393    this.rowEL = rowEL;
394    this.x = x;
395    this.y = y;
396    this.ns = ns;
397    this.offsetY = offsetY;
398    this.isRight = isRight;
399    this.business = business;
400  }
401}
402
403export class BaseStruct {
404  translateY: number | undefined;
405  frame: Rect | undefined;
406  isHover: boolean = false;
407}
408
409export function drawLines(ctx: CanvasRenderingContext2D, xs: Array<any>, height: number, lineColor: string) {
410  if (ctx) {
411    ctx.beginPath();
412    ctx.lineWidth = 1;
413    ctx.strokeStyle = lineColor || '#dadada';
414    xs?.forEach((it) => {
415      ctx.moveTo(Math.floor(it), 0);
416      ctx.lineTo(Math.floor(it), height);
417    });
418    ctx.stroke();
419    ctx.closePath();
420  }
421}
422
423export function drawFlagLine(
424  commonCtx: any,
425  hoverFlag: any,
426  selectFlag: any,
427  startNS: number,
428  endNS: number,
429  totalNS: number,
430  frame: any,
431  slicesTime:
432    | {
433        startTime: number | null | undefined;
434        endTime: number | null | undefined;
435        color: string | null | undefined;
436      }
437    | undefined
438) {
439  if (commonCtx) {
440    if (hoverFlag) {
441      commonCtx.beginPath();
442      commonCtx.lineWidth = 2;
443      commonCtx.strokeStyle = hoverFlag?.color || '#dadada';
444      commonCtx.moveTo(Math.floor(hoverFlag.x), 0);
445      commonCtx.lineTo(Math.floor(hoverFlag.x), frame.height);
446      commonCtx.stroke();
447      commonCtx.closePath();
448    }
449    if (selectFlag) {
450      commonCtx.beginPath();
451      commonCtx.lineWidth = 2;
452      commonCtx.strokeStyle = selectFlag?.color || '#dadada';
453      selectFlag.x = ns2x(selectFlag.time, startNS, endNS, totalNS, frame);
454      commonCtx.moveTo(Math.floor(selectFlag.x), 0);
455      commonCtx.lineTo(Math.floor(selectFlag.x), frame.height);
456      commonCtx.stroke();
457      commonCtx.closePath();
458    }
459    if (slicesTime && slicesTime.startTime && slicesTime.endTime) {
460      commonCtx.beginPath();
461      commonCtx.lineWidth = 1;
462      commonCtx.strokeStyle = slicesTime.color || '#dadada';
463      let x1 = ns2x(slicesTime.startTime, startNS, endNS, totalNS, frame);
464      let x2 = ns2x(slicesTime.endTime, startNS, endNS, totalNS, frame);
465      commonCtx.moveTo(Math.floor(x1), 0);
466      commonCtx.lineTo(Math.floor(x1), frame.height);
467      commonCtx.moveTo(Math.floor(x2), 0);
468      commonCtx.lineTo(Math.floor(x2), frame.height);
469      commonCtx.stroke();
470      commonCtx.closePath();
471    }
472  }
473}
474
475export function drawFlagLineSegment(ctx: any, hoverFlag: any, selectFlag: any, frame: any, tse: TimerShaftElement) {
476  if (ctx) {
477    if (hoverFlag) {
478      ctx.beginPath();
479      ctx.lineWidth = 2;
480      ctx.strokeStyle = hoverFlag?.color || '#dadada';
481      ctx.moveTo(Math.floor(hoverFlag.x), 0);
482      ctx.lineTo(Math.floor(hoverFlag.x), frame.height);
483      ctx.stroke();
484      ctx.closePath();
485    }
486    if (selectFlag) {
487      ctx.beginPath();
488      ctx.lineWidth = 2;
489      ctx.strokeStyle = selectFlag?.color || '#dadada';
490      selectFlag.x = ns2x(
491        selectFlag.time,
492        TraceRow.range!.startNS,
493        TraceRow.range!.endNS,
494        TraceRow.range!.totalNS,
495        frame
496      );
497      ctx.moveTo(Math.floor(selectFlag.x), 0);
498      ctx.lineTo(Math.floor(selectFlag.x), frame.height);
499      ctx.stroke();
500      ctx.closePath();
501    }
502    tse.sportRuler!.slicesTimeList.forEach((slicesTime) => {
503      if (slicesTime && slicesTime.startTime && slicesTime.endTime) {
504        ctx.beginPath();
505        ctx.lineWidth = 1;
506        ctx.strokeStyle = slicesTime.color || '#dadada';
507        let x1 = ns2x(slicesTime.startTime, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, frame);
508        let x2 = ns2x(slicesTime.endTime, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, frame);
509        // 划线逻辑
510        ctx.moveTo(Math.floor(x1), 0);
511        ctx.lineTo(Math.floor(x1), frame.height); //左边的线
512        ctx.moveTo(Math.floor(x2), 0);
513        ctx.lineTo(Math.floor(x2), frame.height); // 右边的线
514        ctx.stroke();
515        ctx.closePath();
516      }
517    });
518  }
519}
520
521export function drawSelection(context: any, params: any) {
522  if (params.isRangeSelect && params.rangeSelectObject) {
523    params.rangeSelectObject!.startX = Math.floor(
524      ns2x(params.rangeSelectObject!.startNS!, params.startNS, params.endNS, params.totalNS, params.frame)
525    );
526    params.rangeSelectObject!.endX = Math.floor(
527      ns2x(params.rangeSelectObject!.endNS!, params.startNS, params.endNS, params.totalNS, params.frame)
528    );
529    if (context) {
530      context.globalAlpha = 0.5;
531      context.fillStyle = '#666666';
532      context.fillRect(
533        params.rangeSelectObject!.startX!,
534        params.frame.y,
535        params.rangeSelectObject!.endX! - params.rangeSelectObject!.startX!,
536        params.frame.height
537      );
538      context.globalAlpha = 1;
539    }
540  }
541}
542
543// draw range select
544export function drawSelectionRange(context: any, params: TraceRow<any>) {
545  if (params.rangeSelect && TraceRow.rangeSelectObject) {
546    TraceRow.rangeSelectObject!.startX = Math.floor(
547      ns2x(
548        TraceRow.rangeSelectObject!.startNS!,
549        TraceRow.range?.startNS ?? 0,
550        TraceRow.range?.endNS ?? 0,
551        TraceRow.range?.totalNS ?? 0,
552        params.frame
553      )
554    );
555    TraceRow.rangeSelectObject!.endX = Math.floor(
556      ns2x(
557        TraceRow.rangeSelectObject!.endNS!,
558        TraceRow.range?.startNS ?? 0,
559        TraceRow.range?.endNS ?? 0,
560        TraceRow.range?.totalNS ?? 0,
561        params.frame
562      )
563    );
564    if (context) {
565      context.globalAlpha = 0.5;
566      context.fillStyle = '#666666';
567      context.fillRect(
568        TraceRow.rangeSelectObject!.startX!,
569        params.frame.y,
570        TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!,
571        params.frame.height
572      );
573      context.globalAlpha = 1;
574    }
575  }
576}
577
578export function drawWakeUp(
579  context: CanvasRenderingContext2D | any,
580  wake: WakeupBean | undefined | null,
581  startNS: number,
582  endNS: number,
583  totalNS: number,
584  frame: Rect,
585  selectCpuStruct: CpuStruct | undefined = undefined,
586  currentCpu: number | undefined = undefined,
587  noVerticalLine = false
588) {
589  if (wake) {
590    let x1 = Math.floor(ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame));
591    context.beginPath();
592    context.lineWidth = 2;
593    context.fillStyle = '#000000';
594    if (x1 > 0 && x1 < frame.x + frame.width) {
595      if (!noVerticalLine) {
596        context.moveTo(x1, frame.y);
597        context.lineTo(x1, frame.y + frame.height);
598      }
599      if (currentCpu == wake.cpu) {
600        let centerY = Math.floor(frame.y + frame.height / 2);
601        context.moveTo(x1, centerY - 6);
602        context.lineTo(x1 + 4, centerY);
603        context.lineTo(x1, centerY + 6);
604        context.lineTo(x1 - 4, centerY);
605        context.lineTo(x1, centerY - 6);
606        context.fill();
607      }
608    }
609    if (selectCpuStruct) {
610      let x2 = Math.floor(ns2x(selectCpuStruct.startTime || 0, startNS, endNS, totalNS, frame));
611      let y = frame.y + frame.height - 10;
612      context.moveTo(x1, y);
613      context.lineTo(x2, y);
614
615      let s = ns2s((selectCpuStruct.startTime || 0) - (wake.wakeupTime || 0));
616      let distance = x2 - x1;
617      if (distance > 12) {
618        context.moveTo(x1, y);
619        context.lineTo(x1 + 6, y - 3);
620        context.moveTo(x1, y);
621        context.lineTo(x1 + 6, y + 3);
622        context.moveTo(x2, y);
623        context.lineTo(x2 - 6, y - 3);
624        context.moveTo(x2, y);
625        context.lineTo(x2 - 6, y + 3);
626        let measure = context.measureText(s);
627        let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
628        let xStart = x1 + Math.floor(distance / 2 - measure.width / 2);
629        if (distance > measure.width + 4) {
630          context.fillStyle = '#ffffff';
631          context.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4);
632          context.font = '10px solid';
633          context.fillStyle = '#000000';
634          context.textBaseline = 'bottom';
635          context.fillText(s, xStart, y - 2);
636        }
637      }
638    }
639    context.strokeStyle = '#000000';
640    context.stroke();
641    context.closePath();
642  }
643}
644
645const wid = 5;
646const linkLineColor = '#ff0000';
647export function drawLinkLines(
648  context: CanvasRenderingContext2D,
649  nodes: PairPoint[][],
650  tm: TimerShaftElement,
651  isFavorite: boolean
652) {
653  let percentage =
654    (tm.getRange()!.totalNS - Math.abs(tm.getRange()!.endNS - tm.getRange()!.startNS)) / tm.getRange()!.totalNS;
655  let maxWidth = tm.getBoundingClientRect().width - 268;
656  for (let i = 0; i < nodes.length; i++) {
657    let it = nodes[i];
658    if (it[0].hidden) {
659      continue;
660    }
661    if (isFavorite) {
662      if (!it[0].rowEL.collect && !it[1].rowEL.collect) {
663        continue;
664      }
665    }
666    switch (it[0].lineType) {
667      case LineType.brokenLine:
668        drawBrokenLine(it, maxWidth, context);
669        break;
670      case LineType.bezierCurve:
671        drawBezierCurve(it, maxWidth, context, percentage);
672        break;
673      default:
674        drawBezierCurve(it, maxWidth, context, percentage);
675    }
676  }
677}
678
679function drawBezierCurve(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D, percentage: number) {
680  let start = it[0].x > it[1].x ? it[1] : it[0];
681  let end = it[0].x > it[1].x ? it[0] : it[1];
682  if (start && end) {
683    //左移到边界,不画线
684    if (start.x <= 0) {
685      start.x = -100;
686    }
687    if (end.x <= 0) {
688      end.x = -100;
689    }
690    //右移到边界,不画线
691    if (start.x >= maxWidth) {
692      start.x = maxWidth + 100;
693    }
694    if (end.x >= maxWidth) {
695      end.x = maxWidth + 100;
696    }
697    context.beginPath();
698    context.lineWidth = 2;
699    context.fillStyle = linkLineColor;
700    context.strokeStyle = linkLineColor;
701    let x0;
702    let y0;
703    let x1;
704    let x2;
705    let y1;
706    let y2;
707    let x3;
708    let y3;
709    x0 = start.x ?? 0;
710    y0 = start.y ?? 0;
711    x3 = end.x ?? 0;
712    y3 = end.y ?? 0;
713    if (end.isRight) {
714      x2 = x3 - 100 * percentage;
715    } else {
716      x2 = x3 + 100 * percentage;
717    }
718    y2 = y3 - 40 * percentage;
719    if (start.isRight) {
720      x1 = x0 - 100 * percentage;
721    } else {
722      x1 = x0 + 100 * percentage;
723    }
724    y1 = y0 + 40 * percentage;
725    //向右箭头终点在x轴正向有偏移
726    if (!start.isRight) {
727      x0 -= 5;
728    }
729    context.moveTo(x0, y0);
730    //箭头向左还是向右
731    if (start.isRight) {
732      context.lineTo(x0 - wid, y0 + wid);
733      context.moveTo(x0, y0);
734      context.lineTo(x0 - wid, y0 - wid);
735    } else {
736      context.lineTo(x0 + wid, y0 + wid);
737      context.moveTo(x0, y0);
738      context.lineTo(x0 + wid, y0 - wid);
739    }
740    context.moveTo(x0, y0);
741    context.bezierCurveTo(x1, y1, x2, y2, x3, y3);
742    context.moveTo(x3, y3);
743    //箭头向左还是向右
744    if (end.isRight) {
745      context.lineTo(x3 - wid, y3 + wid);
746      context.moveTo(x3, y3);
747      context.lineTo(x3 - wid, y3 - wid);
748    } else {
749      context.lineTo(x3 + wid, y3 + wid);
750      context.moveTo(x3, y3);
751      context.lineTo(x3 + wid, y3 - wid);
752    }
753    context.moveTo(x3, y3);
754    context.stroke();
755    context.closePath();
756  }
757}
758
759function drawBrokenLine(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D) {
760  let start = it[0].x > it[1].x ? it[1] : it[0];
761  let end = it[0].x > it[1].x ? it[0] : it[1];
762  if (start && end) {
763    if (start.x <= 0) {
764      start.x = -100;
765    }
766    if (end.x <= 0) {
767      end.x = -100;
768    }
769    //右移到边界,不画线
770    if (start.x >= maxWidth) {
771      start.x = maxWidth + 100;
772    }
773    if (end.x >= maxWidth) {
774      end.x = maxWidth + 100;
775    }
776    context.beginPath();
777    context.lineWidth = 2;
778    context.fillStyle = '#46B1E3';
779    context.strokeStyle = '#46B1E3';
780    let x0;
781    let y0;
782    let x1;
783    let y1;
784    let x2;
785    let y2;
786    x0 = start.x ?? 0;
787    y0 = start.y ?? 0;
788    y2 = end.y ?? 0;
789    x2 = end.x ?? 0;
790    let leftEndpointX;
791    let leftEndpointY;
792    let rightEndpointX;
793    let rightEndpointY;
794
795    if (start.y < end.y) {
796      x1 = start.x ?? 0;
797      y1 = end.y ?? 0;
798      leftEndpointX = x2 - wid;
799      leftEndpointY = y2 - wid;
800      rightEndpointX = x2 - wid;
801      rightEndpointY = y2 + wid;
802    } else {
803      x2 = end.x - wid ?? 0;
804      x1 = end.x - wid ?? 0;
805      y1 = start.y ?? 0;
806      leftEndpointX = x2 - wid;
807      leftEndpointY = y2 + wid;
808      rightEndpointX = x2 + wid;
809      rightEndpointY = y2 + wid;
810    }
811    context.moveTo(x0, y0);
812    context.lineTo(x1, y1);
813    context.lineTo(x2, y2);
814    context.stroke();
815    context.closePath();
816    context.beginPath();
817    context.lineWidth = 2;
818    context.fillStyle = '#46B1E3';
819    context.strokeStyle = '#46B1E3';
820    context.moveTo(x2, y2);
821    context.lineTo(leftEndpointX, leftEndpointY);
822    context.lineTo(rightEndpointX, rightEndpointY);
823    context.lineTo(x2, y2);
824    context.fill();
825    context.closePath();
826  }
827}
828
829export function drawLoading(
830  ctx: CanvasRenderingContext2D,
831  startNS: number,
832  endNS: number,
833  totalNS: number,
834  frame: any,
835  left: number,
836  right: number
837) {}
838
839export function drawString(ctx: CanvasRenderingContext2D, str: string, textPadding: number, frame: Rect, data: any) {
840  if (data.textMetricsWidth === undefined) {
841    data.textMetricsWidth = ctx.measureText(str).width;
842  }
843  let charWidth = Math.round(data.textMetricsWidth / str.length);
844  let fillTextWidth = frame.width - textPadding * 2;
845  if (data.textMetricsWidth < fillTextWidth) {
846    let x2 = Math.floor(frame.width / 2 - data.textMetricsWidth / 2 + frame.x + textPadding);
847    ctx.fillText(str, x2, Math.floor(frame.y + frame.height / 2), fillTextWidth);
848  } else {
849    if (fillTextWidth >= charWidth) {
850      let chatNum = fillTextWidth / charWidth;
851      let x1 = frame.x + textPadding;
852      if (chatNum < 2) {
853        ctx.fillText(str.substring(0, 1), x1, Math.floor(frame.y + frame.height / 2), fillTextWidth);
854      } else {
855        ctx.fillText(str.substring(0, chatNum - 1) + '...', x1, Math.floor(frame.y + frame.height / 2), fillTextWidth);
856      }
857    }
858  }
859}
860
861export function hiPerf(
862  arr: Array<any>,
863  arr2: Array<any>,
864  res: Array<any>,
865  startNS: number,
866  endNS: number,
867  frame: any,
868  groupBy10MS: boolean,
869  use: boolean
870) {
871  if (use && res.length > 0) {
872    let pns = (endNS - startNS) / frame.width;
873    let y = frame.y;
874    for (let i = 0; i < res.length; i++) {
875      let it = res[i];
876      if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
877        if (!it.frame) {
878          it.frame = {};
879          it.frame.y = y;
880        }
881        it.frame.height = it.height;
882        HiPerfStruct.setFrame(it, pns, startNS, endNS, frame);
883      } else {
884        it.frame = null;
885      }
886    }
887    return;
888  }
889  res.length = 0;
890  if (arr) {
891    let list = groupBy10MS ? arr2 : arr;
892    let pns = (endNS - startNS) / frame.width;
893    let y = frame.y;
894    for (let i = 0, len = list.length; i < len; i++) {
895      let it = list[i];
896      if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
897        if (!list[i].frame) {
898          list[i].frame = {};
899          list[i].frame.y = y;
900        }
901        list[i].frame.height = it.height;
902        HiPerfStruct.setFrame(list[i], pns, startNS, endNS, frame);
903        if (groupBy10MS) {
904          if (
905            i > 0 &&
906            (list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) &&
907            (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0) &&
908            (list[i - 1].frame?.height || 0) == (list[i].frame?.height || 0)
909          ) {
910          } else {
911            res.push(list[i]);
912          }
913        } else {
914          if (i > 0 && Math.abs((list[i - 1].frame?.x || 0) - (list[i].frame?.x || 0)) < 4) {
915          } else {
916            res.push(list[i]);
917          }
918        }
919      }
920    }
921  }
922}
923
924export class HiPerfStruct extends BaseStruct {
925  static hoverStruct: HiPerfStruct | undefined;
926  static selectStruct: HiPerfStruct | undefined;
927  id: number | undefined;
928  callchain_id: number | undefined;
929  timestamp: number | undefined;
930  thread_id: number | undefined;
931  event_count: number | undefined;
932  event_type_id: number | undefined;
933  cpu_id: number | undefined;
934  thread_state: string | undefined;
935  startNS: number | undefined;
936  endNS: number | undefined;
937  dur: number | undefined;
938  height: number | undefined;
939
940  static drawRoundRectPath(cxt: Path2D, x: number, y: number, width: number, height: number, radius: number) {
941    cxt.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2);
942    cxt.lineTo(x + radius, y + height);
943    cxt.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI);
944    cxt.lineTo(x + 0, y + radius);
945    cxt.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
946    cxt.lineTo(x + width - radius, y + 0);
947    cxt.arc(x + width - radius, y + radius, radius, (Math.PI * 3) / 2, Math.PI * 2);
948    cxt.lineTo(x + width, y + height - radius);
949    cxt.moveTo(x + width / 3, y + height / 5);
950    cxt.lineTo(x + width / 3, y + (height / 5) * 4);
951    cxt.moveTo(x + width / 3, y + height / 5);
952    cxt.bezierCurveTo(
953      x + width / 3 + 7,
954      y + height / 5 - 2,
955      x + width / 3 + 7,
956      y + height / 5 + 6,
957      x + width / 3,
958      y + height / 5 + 4
959    );
960  }
961
962  static draw(ctx: CanvasRenderingContext2D, path: Path2D, data: any, groupBy10MS: boolean) {
963    if (data.frame) {
964      if (groupBy10MS) {
965        let width = data.frame.width;
966        path.rect(data.frame.x, 40 - (data.height || 0), width, data.height || 0);
967      } else {
968        path.moveTo(data.frame.x + 7, 20);
969        HiPerfStruct.drawRoundRectPath(path, data.frame.x - 7, 20 - 7, 14, 14, 3);
970        path.moveTo(data.frame.x, 27);
971        path.lineTo(data.frame.x, 33);
972      }
973    }
974  }
975
976  static setFrame(node: any, pns: number, startNS: number, endNS: number, frame: any) {
977    if ((node.startNS || 0) < startNS) {
978      node.frame.x = 0;
979    } else {
980      node.frame.x = Math.floor(((node.startNS || 0) - startNS) / pns);
981    }
982    if ((node.startNS || 0) + (node.dur || 0) > endNS) {
983      node.frame.width = frame.width - node.frame.x;
984    } else {
985      node.frame.width = Math.ceil(((node.startNS || 0) + (node.dur || 0) - startNS) / pns - node.frame.x);
986    }
987    if (node.frame.width < 1) {
988      node.frame.width = 1;
989    }
990  }
991
992  static groupBy10MS(array: Array<any>, intervalPerf: number, maxCpu?: number | undefined): Array<any> {
993    let obj = array
994      .map((it) => {
995        it.timestamp_group = Math.trunc(it.startNS / 1_000_000_0) * 1_000_000_0;
996        return it;
997      })
998      .reduce((pre, current) => {
999        (pre[current['timestamp_group']] = pre[current['timestamp_group']] || []).push(current);
1000        return pre;
1001      }, {});
1002    let arr = [];
1003    for (let aKey in obj) {
1004      let ns = parseInt(aKey);
1005      let height: number = 0;
1006      if (maxCpu != undefined) {
1007        height = Math.floor((obj[aKey].length / (10 / intervalPerf) / maxCpu) * 40);
1008      } else {
1009        height = Math.floor((obj[aKey].length / (10 / intervalPerf)) * 40);
1010      }
1011      arr.push({
1012        startNS: ns,
1013        dur: 1_000_000_0,
1014        height: height,
1015      });
1016    }
1017    return arr;
1018  }
1019}
1020
1021function setMemFrame(node: any, padding: number, startNS: number, endNS: number, totalNS: number, frame: any) {
1022  let x1: number;
1023  let x2: number;
1024  if ((node.startTime || 0) <= startNS) {
1025    x1 = 0;
1026  } else {
1027    x1 = ns2x(node.startTime || 0, startNS, endNS, totalNS, frame);
1028  }
1029  if ((node.startTime || 0) + (node.duration || 0) >= endNS) {
1030    x2 = frame.width;
1031  } else {
1032    x2 = ns2x((node.startTime || 0) + (node.duration || 0), startNS, endNS, totalNS, frame);
1033  }
1034  let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1;
1035  if (!node.frame) {
1036    node.frame = {};
1037  }
1038  node.frame.x = Math.floor(x1);
1039  node.frame.y = Math.floor(frame.y + padding);
1040  node.frame.width = Math.ceil(getV);
1041  node.frame.height = Math.floor(frame.height - padding * 2);
1042}
1043
1044export function mem(
1045  list: Array<any>,
1046  memFilter: Array<any>,
1047  startNS: number,
1048  endNS: number,
1049  totalNS: number,
1050  frame: any,
1051  use: boolean
1052) {
1053  if (use && memFilter.length > 0) {
1054    for (let i = 0, len = memFilter.length; i < len; i++) {
1055      if (
1056        (memFilter[i].startTime || 0) + (memFilter[i].duration || 0) > startNS &&
1057        (memFilter[i].startTime || 0) < endNS
1058      ) {
1059        setMemFrame(memFilter[i], 5, startNS, endNS, totalNS, frame);
1060      } else {
1061        memFilter[i].frame = null;
1062      }
1063    }
1064    return;
1065  }
1066  memFilter.length = 0;
1067  if (list) {
1068    for (let i = 0, len = list.length; i < len; i++) {
1069      let it = list[i];
1070      if ((it.startTime || 0) + (it.duration || 0) > startNS && (it.startTime || 0) < endNS) {
1071        setMemFrame(list[i], 5, startNS, endNS, totalNS, frame);
1072        if (
1073          i > 0 &&
1074          (list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) &&
1075          (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0)
1076        ) {
1077        } else {
1078          memFilter.push(list[i]);
1079        }
1080      }
1081    }
1082  }
1083}
1084
1085export function drawWakeUpList(
1086  context: CanvasRenderingContext2D | any,
1087  wake: WakeupBean | undefined | null,
1088  startNS: number,
1089  endNS: number,
1090  totalNS: number,
1091  frame: Rect,
1092  wakeup: WakeupBean | undefined = undefined,
1093  currentCpu: number | undefined | null = undefined,
1094  noVerticalLine = false,
1095) {
1096  if (wake) {
1097    let x1 = Math.floor(
1098      ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame)
1099    );
1100    context.beginPath();
1101    context.lineWidth = 2;
1102    context.fillStyle = '#000000';
1103    if (x1 > 0 && x1 < frame.x + frame.width) {
1104      if (!noVerticalLine) {
1105        context.moveTo(x1, frame.y);
1106        context.lineTo(x1, frame.y + frame.height);
1107      }
1108      if (currentCpu == wake.cpu) {
1109        let centerY = Math.floor(frame.y + frame.height / 2);
1110        context.moveTo(x1, centerY - 6);
1111        context.lineTo(x1 + 4, centerY);
1112        context.lineTo(x1, centerY + 6);
1113        context.lineTo(x1 - 4, centerY);
1114        context.lineTo(x1, centerY - 6);
1115        context.fill();
1116      }
1117    }
1118    if (wakeup) {
1119      let x2 = Math.floor(
1120        ns2x(
1121          wakeup.ts || 0,
1122          startNS,
1123          endNS,
1124          totalNS,
1125          frame
1126        )
1127      );
1128      let y = frame.y + frame.height - 10;
1129      context.moveTo(x1, y);
1130      context.lineTo(x2, y);
1131      context.moveTo(x2, y - 25);
1132      context.lineTo(x2, y + 5);
1133
1134      let s = ns2s(
1135        (wakeup.ts || 0) - (wake.wakeupTime || 0)
1136      );
1137      let distance = x2 - x1;
1138      if (distance > 12) {
1139        context.moveTo(x1, y);
1140        context.lineTo(x1 + 6, y - 3);
1141        context.moveTo(x1, y);
1142        context.lineTo(x1 + 6, y + 3);
1143        context.moveTo(x2, y);
1144        context.lineTo(x2 - 6, y - 3);
1145        context.moveTo(x2, y);
1146        context.lineTo(x2 - 6, y + 3);
1147        let measure = context.measureText(s);
1148        let tHeight =
1149          measure.actualBoundingBoxAscent +
1150          measure.actualBoundingBoxDescent;
1151        let xStart = x1 + Math.floor(distance / 2 - measure.width / 2);
1152        if (distance > measure.width + 4) {
1153          context.fillStyle = '#ffffff';
1154          context.fillRect(
1155            xStart - 2,
1156            y - 4 - tHeight,
1157            measure.width + 4,
1158            tHeight + 4
1159          );
1160          context.font = '10px solid';
1161          context.fillStyle = '#000000';
1162          context.textBaseline = 'bottom';
1163          context.fillText(s, xStart, y - 2);
1164        }
1165      }
1166    }
1167    context.strokeStyle = '#000000';
1168    context.stroke();
1169    context.closePath();
1170  }
1171}
1172