• 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 './cpu/ProcedureWorkerCPU';
17import { RangeSelectStruct, TraceRow } from '../../component/trace/base/TraceRow';
18import { TimerShaftElement } from '../../component/trace/TimerShaftElement';
19import { Flag } from '../../component/trace/timer-shaft/Flag';
20import { drawVSync } from '../../component/chart/VSync';
21import { FuncStruct } from './ProcedureWorkerFunc';
22import { ProcessMemStruct } from './ProcedureWorkerMem';
23
24export abstract class Render {
25  abstract renderMainThread(req: unknown, row: unknown): void;
26}
27
28export abstract class PerfRender {
29  abstract render(req: RequestMessage, list: Array<unknown>, filter: Array<unknown>, dataList2: Array<unknown>): void;
30}
31
32export class RequestMessage {
33  type: string | undefined | null;
34  lazyRefresh: boolean | undefined;
35  intervalPerf: unknown;
36  canvas: unknown;
37  context!: CanvasRenderingContext2D;
38  params: unknown;
39  online: unknown;
40  buf: unknown;
41  isRangeSelect!: boolean;
42  isHover!: boolean;
43  xs?: Array<number>;
44  frame!: Rect;
45  flagMoveInfo?: Flag;
46  flagSelectedInfo?: Flag;
47  hoverX: unknown;
48  hoverY: unknown;
49  startNS!: number;
50  endNS!: number;
51  totalNS!: number;
52  slicesTime:
53    | {
54        startTime: number | null;
55        endTime: number | null;
56        color: string | null;
57      }
58    | undefined;
59  range: unknown;
60  scale: unknown;
61  chartColor: unknown;
62  canvasWidth: unknown;
63  canvasHeight: unknown;
64  useCache: unknown;
65  lineColor!: string;
66  wakeupBean: WakeupBean | undefined | null;
67  id: unknown;
68  postMessage:
69    | {
70        (message: unknown, targetOrigin: string, transfer?: Transferable[]): void;
71        (message: unknown, options?: WindowPostMessageOptions): void;
72      }
73    | undefined;
74}
75
76export function ns2s(ns: number): string {
77  let second1 = 1_000_000_000; // 1 second
78  let millisecond = 1_000_000; // 1 millisecond
79  let microsecond = 1_000; // 1 microsecond
80  let res;
81  if (ns >= second1) {
82    res = `${(ns / 1000 / 1000 / 1000).toFixed(1)} s`;
83  } else if (ns >= millisecond) {
84    res = `${(ns / 1000 / 1000).toFixed(1)} ms`;
85  } else if (ns >= microsecond) {
86    res = `${(ns / 1000).toFixed(1)} μs`;
87  } else if (ns > 0) {
88    res = `${ns.toFixed(1)} ns`;
89  } else {
90    res = `${ns.toFixed(0)}`;
91  }
92  return res;
93}
94
95export function ns2Timestamp(ns: number): string {
96  let hour = Math.floor(ns / 3600000000000);
97  let minute = Math.floor((ns % 3600000000000) / 60000000000);
98  let second = Math.floor((ns % 60000000000) / 1000000000);
99  let millisecond = Math.floor((ns % 1000000000) / 1000000);
100  let microsecond = Math.floor((ns % 1000000) / 1000);
101  let nanosecond = ns % 1000;
102  return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second
103    .toString()
104    .padStart(2, '0')}:${millisecond.toString().padStart(3, '0')}:${microsecond
105    .toString()
106    .padStart(3, '0')}:${nanosecond.toString().padStart(3, '0')}`;
107}
108
109const offsetX = 5;
110
111export function isFrameContainPoint(
112  frame: Rect,
113  x: number,
114  y: number,
115  strict: boolean = true,
116  offset: boolean = false
117): boolean {
118  if (strict) {
119    if (offset) {
120      return (
121        x >= frame.x - offsetX && x <= frame.x + frame.width + offsetX && y >= frame.y && y <= frame.y + frame.height
122      );
123    } else {
124      return x >= frame.x && x <= frame.x + frame.width && y >= frame.y && y <= frame.y + frame.height;
125    }
126  } else {
127    if (offset) {
128      return x >= frame.x - offsetX && x <= frame.x + frame.width + offsetX;
129    } else {
130      return x >= frame.x && x <= frame.x + frame.width;
131    }
132  }
133}
134
135export const isSurroundingPoint = function (pointX: number, currentRect: Rect, unitPointXRange: number): boolean {
136  return pointX >= currentRect?.x - unitPointXRange && pointX <= currentRect?.x + unitPointXRange;
137};
138
139export const computeUnitWidth = function (
140  preTs: number,
141  currentTs: number,
142  frameWidth: number,
143  selectUnitWidth: number
144): number {
145  let max = 150;
146  let unitWidth = ((currentTs - preTs) * frameWidth) / (TraceRow.range!.endNS - TraceRow.range!.startNS);
147  if (unitWidth < selectUnitWidth) {
148    return unitWidth > max || unitWidth === 0 ? max : unitWidth;
149  }
150  return selectUnitWidth > max || selectUnitWidth === 0 ? max : selectUnitWidth;
151};
152
153class FilterConfig {
154  startNS: number = 0;
155  endNS: number = 0;
156  totalNS: number = 0;
157  frame: Rect = new Rect(0, 0, 0, 0);
158  useCache: boolean = false;
159  startKey: string = 'startNS';
160  durKey: string = 'dur';
161  paddingTop: number = 0;
162}
163
164interface CommonStruct {
165  frame: Rect;
166}
167
168export function fillCacheData(filterList: Array<unknown>, condition: FilterConfig): boolean {
169  if (condition.useCache && filterList.length > 0) {
170    let pns = (condition.endNS - condition.startNS) / condition.frame.width;
171    let y = condition.frame.y + condition.paddingTop;
172    let height = condition.frame.height - condition.paddingTop * 2;
173    for (let i = 0, len = filterList.length; i < len; i++) {
174      let it = filterList[i] as BaseStruct;
175      if (
176        //@ts-ignore
177        (it[condition.startKey] || 0) + (it[condition.durKey] || 0) > condition.startNS &&
178        //@ts-ignore
179        (it[condition.startKey] || 0) < condition.endNS
180      ) {
181        if (!it.frame) {
182          it.frame = new Rect(0, 0, 0, 0);
183          it.frame.y = y;
184          it.frame.height = height;
185        }
186        setNodeFrame(
187          it,
188          pns,
189          condition.startNS,
190          condition.endNS,
191          condition.frame,
192          condition.startKey,
193          condition.durKey
194        );
195      } else {
196        it.frame = undefined;
197      }
198    }
199    return true;
200  }
201  return false;
202}
203
204export function fillCacheDataIdx(filterData: Array<unknown>, slice: number[], condition: FilterConfig): boolean {
205  if (condition.useCache && filterData.length > 0) {
206    let pns = (condition.endNS - condition.startNS) / condition.frame.width;
207    let y = condition.frame.y + condition.paddingTop;
208    let height = condition.frame.height - condition.paddingTop * 2;
209    for (let i = slice[0]; i <= slice[1]; i++) {
210      let it = filterData[i] as BaseStruct;
211      if (!it) {
212        continue;
213      }
214      if (
215        //@ts-ignore
216        (it[condition.startKey] || 0) + (it[condition.durKey] || 0) > condition.startNS &&
217        //@ts-ignore
218        (it[condition.startKey] || 0) < condition.endNS
219      ) {
220        if (!it.frame) {
221          it.frame = new Rect(0, 0, 0, 0);
222          it.frame.y = y;
223          it.frame.height = height;
224        }
225        setNodeFrame(
226          it,
227          pns,
228          condition.startNS,
229          condition.endNS,
230          condition.frame,
231          condition.startKey,
232          condition.durKey
233        );
234      } else {
235        it.frame = undefined;
236      }
237    }
238    return true;
239  }
240  return false;
241}
242
243export function bsearch(haystack: ArrayLike<unknown>, needle: FilterConfig): number {
244  return searchImpl(haystack, needle, 0, haystack.length);
245}
246
247function searchImpl(stack: ArrayLike<unknown>, cfg: FilterConfig, i: number, j: number): number {
248  if (i === j) {
249    return -1;
250  }
251  if (i + 1 === j) {
252    //@ts-ignore
253    return cfg.endNS >= stack[i][cfg.startKey] ? i : -1;
254  }
255  const middle = Math.floor((j - i) / 2) + i;
256  //@ts-ignore
257  const middleValue = stack[middle][cfg.startKey];
258  if (cfg.endNS < middleValue) {
259    return searchImpl(stack, cfg, i, middle);
260  } else {
261    return searchImpl(stack, cfg, middle, j);
262  }
263}
264
265export function findRangeIdx(fullData: Array<unknown>, condition: FilterConfig): number[] {
266  //@ts-ignore
267  let a = fullData.findIndex((it) => it[condition.startKey] + it[condition.durKey] >= condition.startNS);
268  let b = bsearch(fullData, condition);
269  return [a, b + 1];
270}
271
272export function findRange(fullData: Array<unknown>, condition: FilterConfig): Array<unknown> {
273  let left = 0;
274  let right = 0;
275  for (let i = 0, j = fullData.length - 1, ib = true, jb = true; i < fullData.length, j >= 0; i++, j--) {
276    //@ts-ignore
277    if (fullData[j][condition.startKey] <= condition.endNS && jb) {
278      right = j;
279      jb = false;
280    }
281    //@ts-ignore
282    if (fullData[i][condition.startKey] + fullData[i][condition.durKey] >= condition.startNS && ib) {
283      left = i;
284      ib = false;
285    }
286    if (!ib && !jb) {
287      break;
288    }
289  }
290  return fullData.slice(left, right + 1);
291}
292
293export const dataFilterHandler = (
294  fullData: Array<BaseStruct>,
295  filterData: Array<BaseStruct>,
296  condition: FilterConfig
297): void => {
298  if (fillCacheData(filterData, condition)) {
299    return;
300  }
301  if (fullData) {
302    filterData.length = 0;
303    let pns = (condition.endNS - condition.startNS) / condition.frame.width; //每个像素多少ns
304    let y = condition.frame.y + condition.paddingTop;
305    let height = condition.frame.height - condition.paddingTop * 2;
306    let slice = findRange(fullData, condition);
307    for (let i = 0; i < slice.length; i++) {
308      const item = slice[i] as BaseStruct;
309      if (!item.frame) {
310        item.frame = new Rect(0, 0, 0, 0);
311        item.frame.y = y;
312        item.frame.height = height;
313      }
314      //@ts-ignore
315      if (item[condition.durKey] === undefined || item[condition.durKey] === null) {
316        if (i === slice.length - 1) {
317          //@ts-ignore
318          item[condition.durKey] = (condition.endNS || 0) - (item[condition.startKey] || 0);
319        } else {
320          //@ts-ignore
321          item[condition.durKey] = (slice[i + 1][condition.startKey] || 0) - (item[condition.startKey] || 0);
322        }
323      }
324      setSliceFrame(slice, condition, pns, i);
325    }
326    //@ts-ignore
327    filterData.push(...slice.filter((it) => it.v));
328  }
329};
330
331function setSliceFrame(slice: Array<unknown>, condition: FilterConfig, pns: number, i: number): void {
332  let sum = 0;
333  //@ts-ignore
334  if (slice[i][condition.durKey] >= pns || slice.length < 100) {
335    //@ts-ignore
336    slice[i].v = true;
337    setNodeFrame(
338      slice[i],
339      pns,
340      condition.startNS,
341      condition.endNS,
342      condition.frame,
343      condition.startKey,
344      condition.durKey
345    );
346  } else {
347    if (i > 0) {
348      //@ts-ignore
349      let c = slice[i][condition.startKey] - slice[i - 1][condition.startKey] - slice[i - 1][condition.durKey];
350      if (c < pns && sum < pns) {
351        //@ts-ignore
352        sum += c + slice[i - 1][condition.durKey];
353        //@ts-ignore
354        slice[i].v = false;
355      } else {
356        //@ts-ignore
357        slice[i].v = true;
358        setNodeFrame(
359          slice[i],
360          pns,
361          condition.startNS,
362          condition.endNS,
363          condition.frame,
364          condition.startKey,
365          condition.durKey
366        );
367        sum = 0;
368      }
369    }
370  }
371}
372
373function setNodeFrame(
374  nodeItem: unknown,
375  pns: number,
376  startNS: number,
377  endNS: number,
378  frame: Rect,
379  startKey: string,
380  durKey: string
381): void {
382  const node = nodeItem as BaseStruct;
383  if (!node.frame) {
384    return;
385  }
386  //@ts-ignore
387  const start = node[startKey] as number;
388  //@ts-ignore
389  const dur = node[durKey] as number;
390  if ((start || 0) < startNS) {
391    node.frame.x = 0;
392  } else {
393    node.frame.x = Math.floor(((start || 0) - startNS) / pns);
394  }
395  if ((start || 0) + (dur || 0) > endNS) {
396    node.frame.width = frame.width - node.frame.x;
397  } else {
398    node.frame.width = Math.ceil(((start || 0) + (dur || 0) - startNS) / pns - node.frame.x);
399  }
400  if (node.frame.width < 1) {
401    node.frame.width = 1;
402  }
403}
404
405export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: Rect): number {
406  if (endNS === 0) {
407    endNS = duration;
408  }
409  let xSize: number = ((ns - startNS) * rect.width) / (endNS - startNS);
410  if (xSize < 0) {
411    xSize = 0;
412  } else if (xSize > rect.width) {
413    xSize = rect.width;
414  }
415  return xSize;
416}
417
418export function nsx(ns: number, width: number): number {
419  let startNS = TraceRow.range?.startNS || 0;
420  let endNS = TraceRow.range?.endNS || 0;
421  let duration = TraceRow.range?.totalNS || 0;
422  if (endNS === 0) {
423    endNS = duration;
424  }
425  let xSize: number = ((ns - startNS) * width) / (endNS - startNS);
426  if (xSize < 0) {
427    xSize = 0;
428  } else if (xSize > width) {
429    xSize = width;
430  }
431  return xSize;
432}
433
434export function ns2xByTimeShaft(ns: number, tse: TimerShaftElement): number {
435  let startNS = tse.getRange()!.startNS;
436  let endNS = tse.getRange()!.endNS;
437  let duration = tse.getRange()!.totalNS;
438  if (endNS === 0) {
439    endNS = duration;
440  }
441  let width = tse.getBoundingClientRect().width - 258;
442  let xSize: number = ((ns - startNS) * width) / (endNS - startNS);
443  if (xSize < 0) {
444    xSize = 0;
445  } else if (xSize > width) {
446    xSize = width;
447  }
448  return xSize;
449}
450
451export class Rect {
452  x: number = 0;
453  y: number = 0;
454  width: number = 0;
455  height: number = 0;
456
457  constructor(x: number, y: number, width: number, height: number) {
458    this.x = x;
459    this.y = y;
460    this.width = width;
461    this.height = height;
462  }
463
464  static intersect(r1: Rect, rect: Rect): boolean {
465    let minX = r1.x <= rect.x ? r1.x : rect.x;
466    let minY = r1.y <= rect.y ? r1.y : rect.y;
467    let maxX = r1.x + r1.width >= rect.x + rect.width ? r1.x + r1.width : rect.x + rect.width;
468    let maxY = r1.y + r1.height >= rect.y + rect.height ? r1.y + r1.height : rect.y + rect.height;
469    return maxX - minX <= rect.width + r1.width && maxY - minY <= r1.height + rect.height;
470  }
471
472  static contains(rect: Rect, x: number, y: number): boolean {
473    return rect.x <= x && x <= rect.x + rect.width && rect.y <= y && y <= rect.y + rect.height;
474  }
475
476  static containsWithMargin(rect: Rect, x: number, y: number, t: number, r: number, b: number, l: number): boolean {
477    return rect.x - l <= x && x <= rect.x + rect.width + r && rect.y - t <= y && y <= rect.y + rect.height + b;
478  }
479
480  static containsWithPadding(
481    rect: Rect,
482    x: number,
483    y: number,
484    paddingLeftRight: number,
485    paddingTopBottom: number
486  ): boolean {
487    return (
488      rect.x + paddingLeftRight <= x &&
489      rect.y + paddingTopBottom <= y &&
490      x <= rect.x + rect.width - paddingLeftRight &&
491      y <= rect.y + rect.height - paddingTopBottom
492    );
493  }
494
495  /**
496   * 判断是否相交
497   * @param rect
498   */
499  intersect(rect: Rect): boolean {
500    let minX = this.x <= rect.x ? this.x : rect.x;
501    let minY = this.y <= rect.y ? this.y : rect.y;
502    let maxX = this.x + this.width >= rect.x + rect.width ? this.x + this.width : rect.x + rect.width;
503    let maxY = this.y + this.height >= rect.y + rect.height ? this.y + this.height : rect.y + rect.height;
504    return maxX - minX <= rect.width + this.width && maxY - minY <= this.height + rect.height;
505  }
506
507  contains(x: number, y: number): boolean {
508    return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height;
509  }
510
511  containsWithMargin(x: number, y: number, t: number, r: number, b: number, l: number): boolean {
512    return this.x - l <= x && x <= this.x + this.width + r && this.y - t <= y && y <= this.y + this.height + b;
513  }
514
515  containsWithPadding(x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean {
516    return (
517      this.x + paddingLeftRight <= x &&
518      x <= this.x + this.width - paddingLeftRight &&
519      this.y + paddingTopBottom <= y &&
520      y <= this.y + this.height - paddingTopBottom
521    );
522  }
523}
524
525export class Point {
526  x: number = 0;
527  y: number = 0;
528  isRight: boolean = true;
529
530  constructor(x: number, y: number, isRight: boolean = true) {
531    this.x = x;
532    this.y = y;
533    this.isRight = isRight;
534  }
535}
536
537export enum LineType {
538  brokenLine,
539  bezierCurve,
540  straightLine,
541}
542
543export class PairPoint {
544  x: number = 0;
545  ns: number = 0;
546  y: number = 0;
547  offsetY: number = 0;
548  rowEL: TraceRow<BaseStruct>;
549  isRight: boolean = true;
550  lineType?: LineType;
551  business: string = '';
552  hidden?: boolean = false;
553  backrowEL?: TraceRow<BaseStruct>;
554
555  constructor(
556    rowEL: TraceRow<BaseStruct>,
557    x: number,
558    y: number,
559    ns: number,
560    offsetY: number,
561    isRight: boolean,
562    business: string
563  ) {
564    this.rowEL = rowEL;
565    this.x = x;
566    this.y = y;
567    this.ns = ns;
568    this.offsetY = offsetY;
569    this.isRight = isRight;
570    this.business = business;
571  }
572}
573
574export class BaseStruct {
575  translateY: number | undefined;
576  frame: Rect | undefined;
577  isHover: boolean = false;
578}
579
580export function drawLines(ctx: CanvasRenderingContext2D, xs: Array<number>, height: number, lineColor: string): void {
581  if (ctx) {
582    ctx.beginPath();
583    ctx.lineWidth = 1;
584    ctx.strokeStyle = lineColor || '#dadada';
585    xs?.forEach((it) => {
586      ctx.moveTo(Math.floor(it), 0);
587      ctx.lineTo(Math.floor(it), height);
588    });
589    ctx.stroke();
590    ctx.closePath();
591  }
592}
593
594export function drawFlagLine(
595  commonCtx: CanvasRenderingContext2D,
596  hoverFlag: Flag,
597  selectFlag: Flag,
598  startNS: number,
599  endNS: number,
600  totalNS: number,
601  frame: Rect,
602  slicesTime:
603    | {
604        startTime: number | null | undefined;
605        endTime: number | null | undefined;
606        color: string | null | undefined;
607      }
608    | undefined
609): void {
610  if (commonCtx) {
611    if (hoverFlag) {
612      setHoverFlag(hoverFlag, commonCtx, frame);
613    }
614    if (selectFlag) {
615      commonCtx.beginPath();
616      commonCtx.lineWidth = 2;
617      commonCtx.strokeStyle = selectFlag?.color || '#dadada';
618      selectFlag.x = ns2x(selectFlag.time, startNS, endNS, totalNS, frame);
619      commonCtx.moveTo(Math.floor(selectFlag.x), 0);
620      commonCtx.lineTo(Math.floor(selectFlag.x), frame.height);
621      commonCtx.stroke();
622      commonCtx.closePath();
623    }
624    if (slicesTime && slicesTime.startTime && slicesTime.endTime) {
625      commonCtx.beginPath();
626      commonCtx.lineWidth = 1;
627      commonCtx.strokeStyle = slicesTime.color || '#dadada';
628      let x1 = ns2x(slicesTime.startTime, startNS, endNS, totalNS, frame);
629      let x2 = ns2x(slicesTime.endTime, startNS, endNS, totalNS, frame);
630      commonCtx.moveTo(Math.floor(x1), 0);
631      commonCtx.lineTo(Math.floor(x1), frame.height);
632      commonCtx.moveTo(Math.floor(x2), 0);
633      commonCtx.lineTo(Math.floor(x2), frame.height);
634      commonCtx.stroke();
635      commonCtx.closePath();
636    }
637  }
638}
639
640export function drawFlagLineSegment(
641  ctx: CanvasRenderingContext2D | null | undefined,
642  hoverFlag: Flag | null | undefined,
643  selectFlag: Flag | null | undefined,
644  frame: Rect,
645  tse: TimerShaftElement
646): void {
647  if (ctx) {
648    setHoverFlag(hoverFlag, ctx, frame);
649    setSelectFlag(selectFlag, ctx, frame);
650    tse.sportRuler!.slicesTimeList.forEach((slicesTime) => {
651      if (slicesTime && slicesTime.startTime && slicesTime.endTime) {
652        ctx.beginPath();
653        ctx.lineWidth = 1;
654        ctx.strokeStyle = slicesTime.color || '#dadada';
655        let x1 = ns2x(
656          slicesTime.startTime,
657          TraceRow.range!.startNS,
658          TraceRow.range!.endNS,
659          TraceRow.range!.totalNS,
660          frame
661        );
662        let x2 = ns2x(
663          slicesTime.endTime,
664          TraceRow.range!.startNS,
665          TraceRow.range!.endNS,
666          TraceRow.range!.totalNS,
667          frame
668        );
669        // 划线逻辑
670        ctx.moveTo(Math.floor(x1), 0);
671        ctx.lineTo(Math.floor(x1), frame.height!); //左边的线
672        ctx.moveTo(Math.floor(x2), 0);
673        ctx.lineTo(Math.floor(x2), frame.height!); // 右边的线
674        ctx.stroke();
675        ctx.closePath();
676      }
677    });
678  }
679}
680
681function setHoverFlag(hoverFlag: Flag | null | undefined, ctx: CanvasRenderingContext2D, frame: Rect): void {
682  if (hoverFlag) {
683    ctx.beginPath();
684    ctx.lineWidth = 2;
685    ctx.strokeStyle = hoverFlag?.color || '#dadada';
686    ctx.moveTo(Math.floor(hoverFlag.x), 0);
687    ctx.lineTo(Math.floor(hoverFlag.x), frame.height);
688    ctx.stroke();
689    ctx.closePath();
690  }
691}
692
693function setSelectFlag(selectFlag: Flag | null | undefined, ctx: CanvasRenderingContext2D, frame: Rect): void {
694  if (selectFlag) {
695    ctx.beginPath();
696    ctx.lineWidth = 2;
697    ctx.strokeStyle = selectFlag?.color || '#dadada';
698    selectFlag.x = ns2x(
699      selectFlag.time,
700      TraceRow.range!.startNS,
701      TraceRow.range!.endNS,
702      TraceRow.range!.totalNS,
703      frame
704    );
705    ctx.moveTo(Math.floor(selectFlag.x), 0);
706    ctx.lineTo(Math.floor(selectFlag.x), frame.height);
707    ctx.stroke();
708    ctx.closePath();
709  }
710}
711
712export function drawLogsLineSegment(
713  ctx: CanvasRenderingContext2D | undefined | null,
714  systemLogFlag: Flag | undefined | null,
715  frame: {
716    x: number;
717    y: number;
718    width: number | undefined;
719    height: number | undefined;
720  },
721  timerShaftEl: TimerShaftElement
722): void {
723  timerShaftEl.sportRuler?.draw();
724  if (systemLogFlag) {
725    if (ctx) {
726      ctx.beginPath();
727      ctx.lineWidth = 2;
728      ctx.strokeStyle = systemLogFlag?.color || '#dadada';
729      ctx.moveTo(Math.floor(systemLogFlag.x), 0);
730      ctx.lineTo(Math.floor(systemLogFlag.x), frame.height || 0);
731      ctx.stroke();
732      ctx.closePath();
733    }
734    if (timerShaftEl.ctx) {
735      let timeText = `| ${ns2Timestamp(systemLogFlag.time)}`;
736      let textPointX = systemLogFlag.x;
737      let textMetrics = timerShaftEl.ctx.measureText(timeText);
738      if (timerShaftEl.ctx.canvas.width - systemLogFlag.x <= textMetrics.width) {
739        textPointX = systemLogFlag.x - textMetrics.width;
740        timeText = `${ns2Timestamp(systemLogFlag.time)} |`;
741      }
742      let locationY = 120;
743      timerShaftEl.ctx.beginPath();
744      timerShaftEl.ctx.lineWidth = 0;
745      timerShaftEl.ctx.fillStyle = '#FFFFFF';
746      let textHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent;
747      timerShaftEl.ctx.fillRect(textPointX, locationY - textHeight, textMetrics.width, textHeight);
748      timerShaftEl.ctx.lineWidth = 2;
749      timerShaftEl.ctx.fillStyle = systemLogFlag?.color || '#dadada';
750      timerShaftEl.ctx.fillText(timeText, textPointX, locationY);
751      timerShaftEl.ctx.stroke();
752      timerShaftEl.ctx.closePath();
753    }
754  }
755}
756
757interface SelectionParams {
758  isRangeSelect: boolean;
759  rangeSelectObject?: RangeSelectStruct;
760  startNS: number;
761  endNS: number;
762  totalNS: number;
763  frame: Rect;
764}
765
766export function drawSelection(ctx: CanvasRenderingContext2D, params: unknown): void {
767  const param = params as SelectionParams;
768  if (param.isRangeSelect && param.rangeSelectObject) {
769    param.rangeSelectObject!.startX = Math.floor(
770      ns2x(param.rangeSelectObject!.startNS!, param.startNS, param.endNS, param.totalNS, param.frame)
771    );
772    param.rangeSelectObject!.endX = Math.floor(
773      ns2x(param.rangeSelectObject!.endNS!, param.startNS, param.endNS, param.totalNS, param.frame)
774    );
775    if (ctx) {
776      ctx.globalAlpha = 0.5;
777      ctx.fillStyle = '#666666';
778      ctx.fillRect(
779        param.rangeSelectObject!.startX!,
780        param.frame.y,
781        param.rangeSelectObject!.endX! - param.rangeSelectObject!.startX!,
782        param.frame.height
783      );
784      ctx.globalAlpha = 1;
785    }
786  }
787}
788
789// draw range select
790export function drawSelectionRange(context: CanvasRenderingContext2D, params: unknown): void {
791  const param = params as TraceRow<BaseStruct>;
792  if (param.rangeSelect && TraceRow.rangeSelectObject) {
793    setStartXEndX(param);
794    if (context) {
795      context.globalAlpha = 0.5;
796      context.fillStyle = '#666666';
797      context.fillRect(
798        TraceRow.rangeSelectObject!.startX!,
799        param.frame.y,
800        TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!,
801        param.frame.height
802      );
803      context.globalAlpha = 1;
804    }
805    // 绘制线程中方法平均帧率的箭头指示线条
806    if (param.avgRateTxt && param.frameRateList && param.frameRateList.length) {
807      drawAvgFrameRate(param.frameRateList, context, param);
808    }
809  }
810}
811
812function setStartXEndX(params: TraceRow<BaseStruct>): void {
813  TraceRow.rangeSelectObject!.startX = Math.floor(
814    ns2x(
815      TraceRow.rangeSelectObject!.startNS!,
816      TraceRow.range?.startNS ?? 0,
817      TraceRow.range?.endNS ?? 0,
818      TraceRow.range?.totalNS ?? 0,
819      params.frame
820    )
821  );
822  TraceRow.rangeSelectObject!.endX = Math.floor(
823    ns2x(
824      TraceRow.rangeSelectObject!.endNS!,
825      TraceRow.range?.startNS ?? 0,
826      TraceRow.range?.endNS ?? 0,
827      TraceRow.range?.totalNS ?? 0,
828      params.frame
829    )
830  );
831}
832
833function setAvgRateStartXEndX(rateList: number[], params: TraceRow<BaseStruct>): number[] {
834  let avgRateStartX = Math.floor(
835    ns2x(
836      rateList[0]!,
837      TraceRow.range?.startNS ?? 0,
838      TraceRow.range?.endNS ?? 0,
839      TraceRow.range?.totalNS ?? 0,
840      params.frame
841    )
842  );
843  let avgRateEndX = Math.floor(
844    ns2x(
845      rateList[rateList.length - 1]!,
846      TraceRow.range?.startNS ?? 0,
847      TraceRow.range?.endNS ?? 0,
848      TraceRow.range?.totalNS ?? 0,
849      params.frame
850    )
851  );
852  return [avgRateStartX, avgRateEndX];
853}
854
855function setTextXY(rateList: number[], params: TraceRow<BaseStruct>, textWidth: number): number[] {
856  let textX =
857    Math.floor(
858      ns2x(
859        (rateList[0]! + rateList[rateList.length - 1]!) / 2,
860        TraceRow.range?.startNS ?? 0,
861        TraceRow.range?.endNS ?? 0,
862        TraceRow.range?.totalNS ?? 0,
863        params.frame
864      )
865    ) -
866    textWidth / 2; // @ts-ignore
867  let textY = params.frame.y + 25;
868  return [textX, textY];
869}
870
871// 转换起始点坐标
872function changeFrameRatePoint(arrList: Array<number>, selectParams: TraceRow<BaseStruct>): number[] {
873  let avgRateStartX = Math.floor(
874    ns2x(
875      arrList[0]!,
876      TraceRow.range?.startNS ?? 0,
877      TraceRow.range?.endNS ?? 0,
878      TraceRow.range?.totalNS ?? 0,
879      selectParams.frame
880    )
881  ); // 起始坐标
882  let avgRateEndX = Math.floor(
883    ns2x(
884      arrList[arrList.length - 1]!,
885      TraceRow.range?.startNS ?? 0,
886      TraceRow.range?.endNS ?? 0,
887      TraceRow.range?.totalNS ?? 0,
888      selectParams.frame
889    )
890  ); // 结束坐标
891  return [avgRateStartX, avgRateEndX];
892}
893
894// 处理文字坐标
895function handleTextCoordinate(arrList: Array<number>, selectParams: TraceRow<BaseStruct>, textWidth: number): number[] {
896  const TEXT_WIDTH_HALF = 2;
897  let textX = Math.floor(
898    ns2x(
899      (arrList[0]! + arrList[arrList.length - 1]!) / 2,
900      TraceRow.range?.startNS ?? 0,
901      TraceRow.range?.endNS ?? 0,
902      TraceRow.range?.totalNS ?? 0,
903      selectParams.frame
904    )
905  ); //根据帧率范围的中间值转换文本的起始x坐标
906  textX = textX <= textWidth / TEXT_WIDTH_HALF ? textX : textX - textWidth / TEXT_WIDTH_HALF; // @ts-ignore
907  let textY = selectParams.frame.y + 11;
908  if (selectParams.avgRateTxt?.includes('HitchTime')) {
909    // @ts-ignore
910    textY = selectParams.frame.y + 11;
911  } else {
912    // 展开时显示在第二行,折叠显示第一行
913    if (selectParams.funcExpand) {
914      // @ts-ignore
915      textY = selectParams.frame.y + 29;
916    } else {
917      // @ts-ignore
918      textY = selectParams.frame.y + 11;
919    }
920  }
921  return [textX, textY];
922}
923
924// 绘制平均帧率箭头指示线条
925export function drawAvgFrameRate(
926  arrList: Array<number>,
927  ctx: CanvasRenderingContext2D,
928  selectParams: TraceRow<BaseStruct>
929): void {
930  let rateList: Array<number> = [...new Set(arrList)];
931  let startX = changeFrameRatePoint(rateList, selectParams)[0];
932  let endX = changeFrameRatePoint(rateList, selectParams)[1];
933  const textWidth = ctx.measureText(selectParams.avgRateTxt!).width;
934
935  const textHeight = 25;
936  const padding = 5;
937  let textX = handleTextCoordinate(rateList, selectParams, textWidth)[0];
938  let textY = handleTextCoordinate(rateList, selectParams, textWidth)[1];
939  //左移到边界,不画线和文字
940  startX = startX <= 0 ? -100 : startX;
941  endX = endX <= 0 ? -100 : endX;
942  textX = textX <= 0 ? -200 : textX;
943  //右移到边界,不画线和文字
944  const ADD_DISTANCE = 100; // @ts-ignore
945  textX = textX + textWidth / 2 >= selectParams.frame.width ? selectParams.frame.width + ADD_DISTANCE : textX; // @ts-ignore
946  startX = startX >= selectParams.frame.width ? selectParams.frame.width + ADD_DISTANCE : startX; // @ts-ignore
947  endX = endX >= selectParams.frame.width ? selectParams.frame.width + ADD_DISTANCE : endX;
948
949  ctx.lineWidth = 2;
950  ctx.strokeStyle = 'yellow';
951  ctx.beginPath();
952  ctx.moveTo(startX, textY);
953  ctx.lineTo(endX, textY);
954  ctx.stroke();
955
956  const arrowSize = 5.5;
957  const arrowHead = (x: number, y: number, direction: 'left' | 'right'): void => {
958    ctx.beginPath();
959    const headX = x + (direction === 'left' ? arrowSize : -arrowSize);
960    const headY = y - arrowSize / 2;
961    ctx.moveTo(x, y);
962    ctx.lineTo(headX, headY);
963    ctx.lineTo(headX, y + arrowSize);
964    ctx.closePath();
965    ctx.fillStyle = 'yellow';
966    ctx.fill();
967  };
968  arrowHead(startX, textY - 1, 'left');
969  arrowHead(endX, textY - 1, 'right');
970
971  const TEXT_RECT_PADDING = 2;
972  ctx.fillStyle = 'red';
973  ctx.fillRect(
974    textX - padding,
975    textY - textHeight / TEXT_RECT_PADDING + padding,
976    textWidth + padding * TEXT_RECT_PADDING,
977    textHeight - padding * TEXT_RECT_PADDING
978  );
979
980  ctx.fillStyle = 'white';
981  ctx.fillText(selectParams.avgRateTxt!, textX, textY + 4);
982}
983
984function drawAvgFrameRateArrow(
985  ctx: CanvasRenderingContext2D,
986  textX: number,
987  textY: number,
988  textWidth: number,
989  startX: number,
990  endX: number,
991  avgFrameRate: string
992): void {
993  const textHeight = 25;
994  const padding = 5;
995  const TEXT_RECT_PADDING = 2;
996  ctx.fillStyle = 'red';
997  ctx.fillRect(
998    textX - padding,
999    textY - textHeight + padding,
1000    textWidth + padding * TEXT_RECT_PADDING,
1001    textHeight - padding * TEXT_RECT_PADDING
1002  );
1003  ctx.lineWidth = 2;
1004  ctx.strokeStyle = 'yellow';
1005  ctx.beginPath();
1006  ctx.moveTo(startX, textY);
1007  ctx.lineTo(endX, textY);
1008  ctx.stroke();
1009  arrowHead(ctx, startX, textY - 1, 'left');
1010  arrowHead(ctx, endX, textY - 1, 'right');
1011  ctx.fillStyle = 'white';
1012  ctx.fillText(avgFrameRate, textX, textY - 8);
1013}
1014
1015const arrowSize = 5.5;
1016const arrowHead = (ctx: CanvasRenderingContext2D, x: number, y: number, direction: 'left' | 'right'): void => {
1017  ctx.beginPath();
1018  const headX = x + (direction === 'left' ? arrowSize : -arrowSize);
1019  const headY = y - arrowSize / 2;
1020  ctx.moveTo(x, y);
1021  ctx.lineTo(headX, headY);
1022  ctx.lineTo(headX, y + arrowSize);
1023  ctx.closePath();
1024  ctx.fillStyle = 'yellow';
1025  ctx.fill();
1026};
1027
1028export function drawWakeUp(
1029  wakeUpContext: CanvasRenderingContext2D | undefined | null,
1030  wake: WakeupBean | undefined | null,
1031  startNS: number,
1032  endNS: number,
1033  totalNS: number,
1034  frame: Rect,
1035  selectCpuStruct: CpuStruct | undefined = undefined,
1036  wakeUpCurrentCpu: number | undefined = undefined,
1037  noVerticalLine = false
1038): void {
1039  if (wake && wakeUpContext) {
1040    let x1 = Math.floor(ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame));
1041    wakeUpContext.beginPath();
1042    wakeUpContext.lineWidth = 2;
1043    wakeUpContext.fillStyle = '#000000';
1044    if (x1 > 0 && x1 < frame.x + frame.width) {
1045      if (!noVerticalLine) {
1046        wakeUpContext.moveTo(x1, frame.y);
1047        wakeUpContext.lineTo(x1, frame.y + frame.height);
1048      }
1049      if (wakeUpCurrentCpu === wake.cpu) {
1050        let centerY = Math.floor(frame.y + frame.height / 2);
1051        wakeUpContext.moveTo(x1, centerY - 6);
1052        wakeUpContext.lineTo(x1 + 4, centerY);
1053        wakeUpContext.lineTo(x1, centerY + 6);
1054        wakeUpContext.lineTo(x1 - 4, centerY);
1055        wakeUpContext.lineTo(x1, centerY - 6);
1056        wakeUpContext.fill();
1057      }
1058    }
1059    if (selectCpuStruct) {
1060      drawWakeUpIfSelect(selectCpuStruct, startNS, endNS, totalNS, frame, wakeUpContext, wake, x1);
1061    }
1062    wakeUpContext.strokeStyle = '#000000';
1063    wakeUpContext.stroke();
1064    wakeUpContext.closePath();
1065  }
1066}
1067
1068function drawWakeUpIfSelect(
1069  selectCpuStruct: CpuStruct,
1070  startNS: number,
1071  endNS: number,
1072  totalNS: number,
1073  frame: Rect,
1074  wakeUpContext: CanvasRenderingContext2D,
1075  wake: unknown,
1076  x1: number
1077): void {
1078  let x2 = Math.floor(ns2x(selectCpuStruct.startTime || 0, startNS, endNS, totalNS, frame));
1079  let y = frame.y + frame.height - 10;
1080  wakeUpContext.moveTo(x1, y);
1081  wakeUpContext.lineTo(x2, y);
1082  //@ts-ignore
1083  let s = ns2s((selectCpuStruct.startTime || 0) - (wake.wakeupTime || 0));
1084  let distance = x2 - x1;
1085  if (distance > 12) {
1086    wakeUpContext.moveTo(x1, y);
1087    wakeUpContext.lineTo(x1 + 6, y - 3);
1088    wakeUpContext.moveTo(x1, y);
1089    wakeUpContext.lineTo(x1 + 6, y + 3);
1090    wakeUpContext.moveTo(x2, y);
1091    wakeUpContext.lineTo(x2 - 6, y - 3);
1092    wakeUpContext.moveTo(x2, y);
1093    wakeUpContext.lineTo(x2 - 6, y + 3);
1094    let measure = wakeUpContext.measureText(s);
1095    let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
1096    let xStart = x1 + Math.floor(distance / 2 - measure.width / 2);
1097    if (distance > measure.width + 4) {
1098      wakeUpContext.fillStyle = '#ffffff';
1099      wakeUpContext.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4);
1100      wakeUpContext.font = '10px solid';
1101      wakeUpContext.fillStyle = '#000000';
1102      wakeUpContext.textBaseline = 'bottom';
1103      wakeUpContext.fillText(s, xStart, y - 2);
1104    }
1105  }
1106}
1107
1108const wid = 5;
1109const linkLineColor = '#ff0000';
1110
1111export function drawLinkLines(
1112  context: CanvasRenderingContext2D,
1113  nodes: PairPoint[][],
1114  tm: TimerShaftElement,
1115  isFavorite: boolean,
1116  favoriteHeight: number
1117): void {
1118  let percentage =
1119    (tm.getRange()!.totalNS - Math.abs(tm.getRange()!.endNS - tm.getRange()!.startNS)) / tm.getRange()!.totalNS;
1120  let maxWidth = tm.getBoundingClientRect().width - 268;
1121  setLinkLinesNodes(nodes, isFavorite, favoriteHeight, maxWidth, context, percentage);
1122}
1123
1124function setLinkLinesNodes(
1125  nodes: PairPoint[][],
1126  isFav: boolean,
1127  favH: number,
1128  max: number,
1129  context: CanvasRenderingContext2D,
1130  perc: number
1131): void {
1132  for (let i = 0; i < nodes.length; i++) {
1133    let it = nodes[i];
1134    const traceRow0 = it[0].rowEL as TraceRow<BaseStruct>;
1135    const traceRow1 = it[1].rowEL as TraceRow<BaseStruct>;
1136    it[0].y = traceRow0.translateY + it[0].offsetY;
1137    it[1].y = traceRow1.translateY + it[1].offsetY;
1138    let newFirstNode = new PairPoint(
1139      traceRow0,
1140      it[0].x,
1141      it[0].y,
1142      it[0].ns,
1143      it[0].offsetY,
1144      it[0].isRight,
1145      it[0].business
1146    );
1147    let newSecondNode = new PairPoint(
1148      traceRow1,
1149      it[1].x,
1150      it[1].y,
1151      it[1].ns,
1152      it[1].offsetY,
1153      it[1].isRight,
1154      it[1].business
1155    );
1156    if (it[0].hidden) {
1157      continue;
1158    }
1159    if (isFav) {
1160      if (traceRow0.collect && traceRow1.collect) {
1161      } else if (!traceRow0.collect && !traceRow1.collect) {
1162        continue;
1163      } else {
1164        traceRow0.collect
1165          ? (newSecondNode.y = Math.max(it[1].y + favH, favH))
1166          : (newFirstNode.y = Math.max(it[0].y + favH, favH));
1167      }
1168    } else {
1169      if (traceRow0.collect && traceRow1.collect) {
1170        continue;
1171      } else if (!traceRow0.collect && !traceRow1.collect) {
1172      } else {
1173        traceRow0.collect ? (newFirstNode.y = it[0].y - favH) : (newSecondNode.y = it[1].y - favH);
1174      }
1175    }
1176    drawLinesByType(it[0].lineType, newFirstNode, newSecondNode, max, context, perc);
1177  }
1178}
1179
1180function drawLinesByType(
1181  lineType: LineType | undefined,
1182  newFirstNode: PairPoint,
1183  newSecondNode: PairPoint,
1184  maxWidth: number,
1185  context: CanvasRenderingContext2D,
1186  percentage: number
1187): void {
1188  switch (lineType) {
1189    case LineType.brokenLine:
1190      drawBrokenLine([newFirstNode, newSecondNode], maxWidth, context);
1191      break;
1192    case LineType.bezierCurve:
1193      drawBezierCurve([newFirstNode, newSecondNode], maxWidth, context, percentage);
1194      break;
1195    case LineType.straightLine:
1196      drawStraightLine([newFirstNode, newSecondNode], maxWidth, context);
1197      break;
1198    default:
1199      drawBezierCurve([newFirstNode, newSecondNode], maxWidth, context, percentage);
1200  }
1201}
1202
1203function drawBezierCurve(
1204  it: PairPoint[],
1205  maxWidth: number,
1206  context: CanvasRenderingContext2D,
1207  percentage: number
1208): void {
1209  let bezierCurveStart = it[0].x > it[1].x ? it[1] : it[0];
1210  let bezierCurveEnd = it[0].x > it[1].x ? it[0] : it[1];
1211  if (bezierCurveStart && bezierCurveEnd) {
1212    //左移到边界,不画线
1213    if (bezierCurveStart.x <= 0) {
1214      bezierCurveStart.x = -100;
1215    }
1216    if (bezierCurveEnd.x <= 0) {
1217      bezierCurveEnd.x = -100;
1218    }
1219    //右移到边界,不画线
1220    if (bezierCurveStart.x >= maxWidth) {
1221      bezierCurveStart.x = maxWidth + 100;
1222    }
1223    if (bezierCurveEnd.x >= maxWidth) {
1224      bezierCurveEnd.x = maxWidth + 100;
1225    }
1226    drawBezierCurveContext(context, bezierCurveStart, bezierCurveEnd, percentage);
1227  }
1228}
1229
1230function drawBezierCurveContext(
1231  context: CanvasRenderingContext2D,
1232  bezierCurveStart: PairPoint,
1233  bezierCurveEnd: PairPoint,
1234  percentage: number
1235): void {
1236  context.beginPath();
1237  context.lineWidth = 2;
1238  context.fillStyle = linkLineColor;
1239  context.strokeStyle = linkLineColor;
1240  let x0 = bezierCurveStart.x ?? 0;
1241  let y0 = bezierCurveStart.y ?? 0;
1242  let x3 = bezierCurveEnd.x ?? 0;
1243  let y3 = bezierCurveEnd.y ?? 0;
1244  let x2 = bezierCurveEnd.isRight ? x3 - 100 * percentage : x3 + 100 * percentage;
1245  let y2 = y3 - 40 * percentage;
1246  let x1 = bezierCurveStart.isRight ? x0 - 100 * percentage : x0 + 100 * percentage;
1247  let y1 = y0 + 40 * percentage;
1248  if (!bezierCurveStart.isRight) {
1249    x0 -= 5;
1250  }
1251  context.moveTo(x0, y0);
1252  if (bezierCurveStart.isRight) {
1253    context.lineTo(x0 - wid, y0 + wid);
1254    context.moveTo(x0, y0);
1255    context.lineTo(x0 - wid, y0 - wid);
1256  } else {
1257    context.lineTo(x0 + wid, y0 + wid);
1258    context.moveTo(x0, y0);
1259    context.lineTo(x0 + wid, y0 - wid);
1260  }
1261  context.moveTo(x0, y0);
1262  context.bezierCurveTo(x1, y1, x2, y2, x3, y3);
1263  context.moveTo(x3, y3);
1264  if (bezierCurveEnd.isRight) {
1265    context.lineTo(x3 - wid, y3 + wid);
1266    context.moveTo(x3, y3);
1267    context.lineTo(x3 - wid, y3 - wid);
1268  } else {
1269    context.lineTo(x3 + wid, y3 + wid);
1270    context.moveTo(x3, y3);
1271    context.lineTo(x3 + wid, y3 - wid);
1272  }
1273  context.moveTo(x3, y3);
1274  context.stroke();
1275  context.closePath();
1276}
1277
1278function drawStraightLine(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D): void {
1279  let startPoint = it[0].x > it[1].x ? it[1] : it[0];
1280  let endPoint = it[0].x > it[1].x ? it[0] : it[1];
1281  let arrowSize = 8;
1282  if (startPoint && endPoint) {
1283    //左移到边界,不画线
1284    if (startPoint.x <= 0) {
1285      startPoint.x = -100;
1286    }
1287    if (endPoint.x <= 0) {
1288      endPoint.x = -100;
1289    }
1290    //右移到边界,不画线
1291    if (startPoint.x >= maxWidth) {
1292      startPoint.x = maxWidth + 100;
1293    }
1294    if (endPoint.x >= maxWidth) {
1295      endPoint.x = maxWidth + 100;
1296    }
1297    drawArrow(context, startPoint, endPoint, arrowSize);
1298  }
1299}
1300
1301function drawArrow(
1302  context: CanvasRenderingContext2D,
1303  startPoint: PairPoint,
1304  endPoint: PairPoint,
1305  arrowSize: number
1306): void {
1307  context.beginPath();
1308  context.lineWidth = 2;
1309  context.strokeStyle = '#0000FF';
1310  context.moveTo(startPoint.x, startPoint.y);
1311  context.lineTo(endPoint.x, endPoint.y);
1312  // 绘制箭头
1313  let arrow = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
1314  context.moveTo(endPoint.x, endPoint.y);
1315  context.lineTo(
1316    endPoint.x - arrowSize * Math.cos(arrow - Math.PI / 6),
1317    endPoint.y - arrowSize * Math.sin(arrow - Math.PI / 6)
1318  );
1319  context.moveTo(endPoint.x, endPoint.y);
1320  context.lineTo(
1321    endPoint.x - arrowSize * Math.cos(arrow + Math.PI / 6),
1322    endPoint.y - arrowSize * Math.sin(arrow + Math.PI / 6)
1323  );
1324  // 绘制另一端箭头
1325  arrow = Math.atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
1326  context.moveTo(startPoint.x, startPoint.y);
1327  context.lineTo(
1328    startPoint.x - arrowSize * Math.cos(arrow - Math.PI / 6),
1329    startPoint.y - arrowSize * Math.sin(arrow - Math.PI / 6)
1330  );
1331  context.moveTo(startPoint.x, startPoint.y);
1332  context.lineTo(
1333    startPoint.x - arrowSize * Math.cos(arrow + Math.PI / 6),
1334    startPoint.y - arrowSize * Math.sin(arrow + Math.PI / 6)
1335  );
1336  context.stroke();
1337  context.closePath();
1338}
1339
1340function drawBrokenLine(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D): void {
1341  let brokenLineStart = it[0].x > it[1].x ? it[1] : it[0];
1342  let brokenLineEnd = it[0].x > it[1].x ? it[0] : it[1];
1343  if (brokenLineStart && brokenLineEnd) {
1344    if (brokenLineStart.x <= 0) {
1345      brokenLineStart.x = -100;
1346    }
1347    if (brokenLineEnd.x <= 0) {
1348      brokenLineEnd.x = -100;
1349    }
1350    if (brokenLineStart.x >= maxWidth) {
1351      brokenLineStart.x = maxWidth + 100;
1352    }
1353    if (brokenLineEnd.x >= maxWidth) {
1354      brokenLineEnd.x = maxWidth + 100;
1355    }
1356    drawBrokenLineContext(context, brokenLineStart, brokenLineEnd);
1357  }
1358}
1359
1360function drawBrokenLineContext(
1361  context: CanvasRenderingContext2D,
1362  brokenLineStart: PairPoint,
1363  brokenLineEnd: PairPoint
1364): void {
1365  context.beginPath();
1366  context.lineWidth = 2;
1367  context.fillStyle = '#46B1E3';
1368  context.strokeStyle = '#46B1E3';
1369  let x0 = brokenLineStart.x ?? 0;
1370  let y0 = brokenLineStart.y ?? 0;
1371  let y2 = brokenLineEnd.y ?? 0;
1372  let x2 = brokenLineEnd.x ?? 0;
1373  let x1;
1374  let y1;
1375  let leftEndpointX;
1376  let leftEndpointY;
1377  let rightEndpointX;
1378  let rightEndpointY;
1379  if (brokenLineStart.y < brokenLineEnd.y) {
1380    x1 = brokenLineStart.x ?? 0;
1381    y1 = brokenLineEnd.y ?? 0;
1382    leftEndpointX = x2 - wid;
1383    leftEndpointY = y2 - wid;
1384    rightEndpointX = x2 - wid;
1385    rightEndpointY = y2 + wid;
1386  } else {
1387    x2 = brokenLineEnd.x - wid ?? 0;
1388    x1 = brokenLineEnd.x - wid ?? 0;
1389    y1 = brokenLineStart.y ?? 0;
1390    leftEndpointX = x2 - wid;
1391    leftEndpointY = y2 + wid;
1392    rightEndpointX = x2 + wid;
1393    rightEndpointY = y2 + wid;
1394  }
1395  context.moveTo(x0, y0);
1396  context.lineTo(x1, y1);
1397  context.lineTo(x2, y2);
1398  context.stroke();
1399  context.closePath();
1400  context.beginPath();
1401  context.lineWidth = 2;
1402  context.fillStyle = '#46B1E3';
1403  context.strokeStyle = '#46B1E3';
1404  context.moveTo(x2, y2);
1405  context.lineTo(leftEndpointX, leftEndpointY);
1406  context.lineTo(rightEndpointX, rightEndpointY);
1407  context.lineTo(x2, y2);
1408  context.fill();
1409  context.closePath();
1410}
1411
1412let loadingText = 'Loading...';
1413let loadingTextWidth = 0;
1414let loadingBackground = '#f1f1f1';
1415let loadingFont = 'bold 11pt Arial';
1416let loadingFontColor = '#696969';
1417
1418export function drawLoadingFrame(
1419  ctx: CanvasRenderingContext2D,
1420  list: Array<unknown>,
1421  traceRow: unknown,
1422  sort: boolean = false
1423): void {
1424  const row = traceRow as TraceRow<BaseStruct>;
1425  ctx.beginPath();
1426  ctx.clearRect(0, 0, row.frame.width, row.frame.height);
1427  drawLines(ctx, TraceRow.range?.xs || [], row.frame.height, '#dadada');
1428  drawVSync(ctx, row.frame.width, row.frame.height);
1429  if (row.loadingFrame) {
1430    if (loadingTextWidth === 0) {
1431      loadingTextWidth = ctx.measureText(loadingText).width;
1432    }
1433    let firstPx = nsx(row.loadingPin1, row.frame.width);
1434    let lastPx = nsx(row.loadingPin2, row.frame.width);
1435    ctx.fillStyle = loadingBackground;
1436    ctx.fillRect(0, 1, firstPx, row.frame.height - 2);
1437    ctx.fillRect(lastPx, 1, row.frame.width - lastPx, row.frame.height - 2);
1438    ctx.fillStyle = loadingFontColor;
1439    if (firstPx > loadingTextWidth) {
1440      ctx.fillText(loadingText, (firstPx - loadingTextWidth) / 2, row.frame.height / 2);
1441    }
1442    if (row.frame.width - lastPx > loadingTextWidth) {
1443      ctx.fillText(loadingText, lastPx + (row.frame.width - lastPx) / 2 - loadingTextWidth / 2, row.frame.height / 2);
1444    }
1445  }
1446  ctx.closePath();
1447}
1448
1449export function drawString(
1450  ctx: CanvasRenderingContext2D,
1451  str: string,
1452  textPadding: number,
1453  frame: Rect,
1454  data: unknown
1455): void {
1456  //@ts-ignore
1457  if (data.textMetricsWidth === undefined) {
1458    //@ts-ignore
1459    data.textMetricsWidth = ctx.measureText(str);
1460  }
1461  //@ts-ignore
1462  const textMetricsWidth = (data.textMetricsWidth as TextMetrics).width;
1463  const yPos = 1.5;
1464  let charWidth = Math.round(textMetricsWidth / str.length);
1465  let fillTextWidth = frame.width - textPadding * 2;
1466  if (textMetricsWidth < fillTextWidth) {
1467    let x2 = Math.floor(frame.width / 2 - textMetricsWidth / 2 + frame.x + textPadding);
1468    ctx.fillText(str, x2, Math.floor(frame.y + frame.height / yPos), fillTextWidth);
1469  } else {
1470    if (fillTextWidth >= charWidth) {
1471      let chatNum = fillTextWidth / charWidth;
1472      let x1 = frame.x + textPadding;
1473
1474      if (chatNum < 2) {
1475        ctx.fillText(str.substring(0, 1), x1, Math.floor(frame.y + frame.height / yPos), fillTextWidth);
1476      } else {
1477        ctx.fillText(
1478          str.substring(0, chatNum - 1) + '...',
1479          x1,
1480          Math.floor(frame.y + frame.height / yPos),
1481          fillTextWidth
1482        );
1483      }
1484    }
1485  }
1486}
1487
1488export function drawFunString(
1489  ctx: CanvasRenderingContext2D,
1490  str: string,
1491  textPadding: number,
1492  frame: Rect,
1493  data: FuncStruct
1494): void {
1495  if (data.textMetricsWidth === undefined) {
1496    data.textMetricsWidth = ctx.measureText(str).width;
1497  }
1498  let charWidth = Math.round(data.textMetricsWidth / str.length);
1499  let fillTextWidth = frame.width - textPadding * 2;
1500  if (data.textMetricsWidth < fillTextWidth) {
1501    let x2 = Math.floor(frame.width / 2 - data.textMetricsWidth / 2 + frame.x + textPadding);
1502    ctx.fillText(str, x2, Math.floor(data.frame!.height * (data.depth! + 0.5) + 3), fillTextWidth);
1503  } else {
1504    if (fillTextWidth >= charWidth) {
1505      let chatNum = fillTextWidth / charWidth;
1506      let x1 = frame.x + textPadding;
1507      if (chatNum < 2) {
1508        ctx.fillText(str.substring(0, 1), x1, Math.floor(data.frame!.height * (data.depth! + 0.5) + 3), fillTextWidth);
1509      } else {
1510        ctx.fillText(
1511          str.substring(0, chatNum - 1) + '...',
1512          x1,
1513          Math.floor(data.frame!.height * (data.depth! + 0.5) + 3),
1514          fillTextWidth
1515        );
1516      }
1517    }
1518  }
1519}
1520
1521export function hiPerf(
1522  arr: Array<HiPerfStruct>,
1523  arr2: Array<HiPerfStruct>,
1524  res: Array<HiPerfStruct>,
1525  startNS: number,
1526  endNS: number,
1527  frame: Rect,
1528  groupBy10MS: boolean,
1529  use: boolean
1530): void {
1531  if (use && res.length > 0) {
1532    setFrameByRes(res, startNS, endNS, frame);
1533    return;
1534  }
1535  res.length = 0;
1536  if (arr) {
1537    setFrameByArr(arr, arr2, res, startNS, endNS, frame, groupBy10MS);
1538  }
1539}
1540
1541function setFrameByRes(res: Array<HiPerfStruct>, startNS: number, endNS: number, frame: Rect): void {
1542  let pns = (endNS - startNS) / frame.width;
1543  let y = frame.y;
1544  for (let i = 0; i < res.length; i++) {
1545    let item = res[i];
1546    if ((item.startNS || 0) + (item.dur || 0) > startNS && (item.startNS || 0) < endNS) {
1547      if (!item.frame) {
1548        item.frame = new Rect(0, 0, 0, 0);
1549        item.frame.y = y;
1550      }
1551      item.frame.height = item.height!;
1552      HiPerfStruct.setFrame(item, pns, startNS, endNS, frame);
1553    } else {
1554      item.frame = undefined;
1555    }
1556  }
1557}
1558
1559function setFrameByArr(
1560  arr: Array<HiPerfStruct>,
1561  arr2: Array<HiPerfStruct>,
1562  res: Array<HiPerfStruct>,
1563  startNS: number,
1564  endNS: number,
1565  frame: Rect,
1566  groupBy10MS: boolean
1567): void {
1568  let list = groupBy10MS ? arr2 : arr;
1569  let pns = (endNS - startNS) / frame.width;
1570  let y = frame.y;
1571  for (let i = 0, len = list.length; i < len; i++) {
1572    let it = list[i];
1573    if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
1574      if (!list[i].frame) {
1575        list[i].frame = new Rect(0, 0, 0, 0);
1576        list[i].frame!.y = y;
1577      }
1578      list[i].frame!.height = it.height!;
1579      HiPerfStruct.setFrame(list[i], pns, startNS, endNS, frame);
1580      setResultArr(groupBy10MS, list, i, res);
1581    }
1582  }
1583}
1584
1585function setResultArr(groupBy10MS: boolean, list: Array<HiPerfStruct>, i: number, res: Array<HiPerfStruct>): void {
1586  const itemI = list[i];
1587  const itemBeforeI = list[i - 1];
1588  if (itemI.frame && itemBeforeI.frame) {
1589    if (groupBy10MS) {
1590      let flag: boolean =
1591        i > 0 &&
1592        (itemBeforeI.frame.x || 0) === (itemI.frame.x || 0) &&
1593        (itemBeforeI.frame.width || 0) === (itemI.frame.width || 0) &&
1594        (itemBeforeI.frame.height || 0) === (itemI.frame.height || 0);
1595      if (!flag) {
1596        res.push(itemI);
1597      }
1598    } else {
1599      if (!(i > 0 && Math.abs((itemBeforeI.frame.x || 0) - (itemI.frame.x || 0)) < 4)) {
1600        res.push(itemI);
1601      }
1602    }
1603  }
1604}
1605
1606export function hiPerf2(filter: Array<HiPerfStruct>, startNS: number, endNS: number, frame: Rect): void {
1607  if (filter.length > 0) {
1608    let pns = (endNS - startNS) / frame.width;
1609    let y = frame.y;
1610    for (let i = 0; i < filter.length; i++) {
1611      let it = filter[i];
1612      if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
1613        if (!it.frame) {
1614          it.frame = new Rect(0, 0, 0, 0);
1615          it.frame.y = y;
1616        }
1617        it.frame.height = it.height!;
1618        HiPerfStruct.setFrame(it, pns, startNS, endNS, frame);
1619      } else {
1620        it.frame = undefined;
1621      }
1622    }
1623    return;
1624  }
1625}
1626
1627export class HiPerfStruct extends BaseStruct {
1628  static hoverStruct: HiPerfStruct | undefined;
1629  static selectStruct: HiPerfStruct | undefined;
1630  id: number | undefined;
1631  callchain_id: number | undefined;
1632  timestamp: number | undefined;
1633  thread_id: number | undefined;
1634  event_count: number | undefined;
1635  event_type_id: number | undefined;
1636  cpu_id: number | undefined;
1637  thread_state: string | undefined;
1638  startNS: number | undefined;
1639  endNS: number | undefined;
1640  dur: number | undefined;
1641  height: number | undefined;
1642  eventCount: number | undefined;
1643  sampleCount: number | undefined;
1644
1645  static drawRoundRectPath(cxt: Path2D, x: number, y: number, width: number, height: number, radius: number): void {
1646    cxt.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2);
1647    cxt.lineTo(x + radius, y + height);
1648    cxt.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI);
1649    cxt.lineTo(x, y + radius);
1650    cxt.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
1651    cxt.lineTo(x + width - radius, y);
1652    cxt.arc(x + width - radius, y + radius, radius, (Math.PI * 3) / 2, Math.PI * 2);
1653    cxt.lineTo(x + width, y + height - radius);
1654    cxt.moveTo(x + width / 3, y + height / 5);
1655    cxt.lineTo(x + width / 3, y + (height / 5) * 4);
1656    cxt.moveTo(x + width / 3, y + height / 5);
1657    cxt.bezierCurveTo(
1658      x + width / 3 + 7,
1659      y + height / 5 - 2,
1660      x + width / 3 + 7,
1661      y + height / 5 + 6,
1662      x + width / 3,
1663      y + height / 5 + 4
1664    );
1665  }
1666
1667  static draw(
1668    ctx: CanvasRenderingContext2D,
1669    normalPath: Path2D,
1670    specPath: Path2D,
1671    data: HiPerfStruct,
1672    groupBy10MS: boolean,
1673    textMetrics?: TextMetrics
1674  ): void {
1675    if (data.frame) {
1676      if (groupBy10MS) {
1677        let width = data.frame.width;
1678        normalPath.rect(data.frame.x, 40 - (data.height || 0), width, data.height || 0);
1679      } else {
1680        data.frame.width > 4 ? (data.frame.width = 4) : (data.frame.width = data.frame.width);
1681        let path = data.callchain_id === -1 ? specPath : normalPath;
1682        path.moveTo(data.frame.x + 7, 20);
1683        if (textMetrics) {
1684          ctx.fillText('��', data.frame.x - textMetrics!.width / 2, 26); //℗©®℗®��
1685        } else {
1686          HiPerfStruct.drawRoundRectPath(path, data.frame.x - 7, 20 - 7, 14, 14, 3);
1687        }
1688        path.moveTo(data.frame.x, 27);
1689        path.lineTo(data.frame.x, 33);
1690      }
1691    }
1692  }
1693
1694  static drawSpecialPath(ctx: CanvasRenderingContext2D, specPath: Path2D): void {
1695    ctx.strokeStyle = '#9fafc4';
1696    ctx.globalAlpha = 0.5;
1697    ctx.stroke(specPath);
1698    ctx.globalAlpha = 1;
1699  }
1700
1701  static setFrame(node: HiPerfStruct, pns: number, startNS: number, endNS: number, frame: Rect): void {
1702    if (!node.frame) {
1703      return;
1704    }
1705    if ((node.startNS || 0) < startNS) {
1706      node.frame.x = 0;
1707    } else {
1708      node.frame.x = Math.floor(((node.startNS || 0) - startNS) / pns);
1709    }
1710    if ((node.startNS || 0) + (node.dur || 0) > endNS) {
1711      node.frame.width = frame.width - node.frame.x;
1712    } else {
1713      node.frame.width = Math.ceil(((node.startNS || 0) + (node.dur || 0) - startNS) / pns - node.frame.x);
1714    }
1715    if (node.frame.width < 1) {
1716      node.frame.width = 1;
1717    }
1718  }
1719
1720  static groupBy10MS(
1721    groupArray: Array<HiPerfStruct>,
1722    intervalPerf: number,
1723    maxCpu?: number | undefined,
1724    usage?: boolean,
1725    event?: number
1726  ): Array<HiPerfStruct> {
1727    let maxEventCount = 0;
1728    let obj = filterGroupArray(groupArray, maxEventCount, usage, event);
1729    let arr = [];
1730    for (let aKey in obj) {
1731      let ns = parseInt(aKey);
1732      let height: number = 0;
1733      if (usage) {
1734        if (maxCpu !== undefined) {
1735          //@ts-ignore
1736          height = Math.floor((obj[aKey].sampleCount / (10 / intervalPerf) / maxCpu) * 40);
1737        } else {
1738          //@ts-ignore
1739          height = Math.floor((obj[aKey].sampleCount / (10 / intervalPerf)) * 40);
1740        }
1741      } else {
1742        //@ts-ignore
1743        height = Math.floor((obj[aKey].eventCount / maxEventCount) * 40);
1744      }
1745      arr.push({
1746        startNS: ns,
1747        dur: 10_000_000,
1748        //@ts-ignore
1749        eventCount: obj[aKey].eventCount,
1750        //@ts-ignore
1751        sampleCount: obj[aKey].sampleCount,
1752        height: height,
1753      });
1754    }
1755    return arr as HiPerfStruct[];
1756  }
1757}
1758
1759function filterGroupArray(
1760  groupArray: Array<HiPerfStruct>,
1761  maxEventCount: number,
1762  usage?: boolean,
1763  event?: number
1764): HiPerfStruct {
1765  const map = groupArray.map((it) => {
1766    //@ts-ignore
1767    it.timestamp_group = Math.trunc(it.startNS / 10_000_000) * 10_000_000;
1768    return it;
1769  });
1770  const reduce = map.reduce((pre: HiPerfStruct, current: HiPerfStruct) => {
1771    if (usage || current.event_type_id === event || event === -1) {
1772      //@ts-ignore
1773      if (pre[current.timestamp_group]) {
1774        //@ts-ignore
1775        pre[current.timestamp_group].sampleCount += 1;
1776        //@ts-ignore
1777        pre[current.timestamp_group].eventCount += current.event_count;
1778      } else {
1779        //@ts-ignore
1780        pre[current.timestamp_group] = {
1781          sampleCount: 1,
1782          eventCount: current.event_count,
1783        };
1784      }
1785      //@ts-ignore
1786      maxEventCount = Math.max(pre[current.timestamp_group].eventCount, maxEventCount);
1787    }
1788    return pre;
1789  }, new HiPerfStruct());
1790  return reduce;
1791}
1792
1793function setMemFrame(
1794  node: ProcessMemStruct,
1795  padding: number,
1796  startNS: number,
1797  endNS: number,
1798  totalNS: number,
1799  frame: Rect
1800): void {
1801  let x1: number;
1802  let x2: number;
1803  if ((node.startTime || 0) <= startNS) {
1804    x1 = 0;
1805  } else {
1806    x1 = ns2x(node.startTime || 0, startNS, endNS, totalNS, frame);
1807  }
1808  if ((node.startTime || 0) + (node.duration || 0) >= endNS) {
1809    x2 = frame.width;
1810  } else {
1811    x2 = ns2x((node.startTime || 0) + (node.duration || 0), startNS, endNS, totalNS, frame);
1812  }
1813  let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1;
1814  if (!node.frame) {
1815    node.frame = new Rect(0, 0, 0, 0);
1816  }
1817  node.frame.x = Math.floor(x1);
1818  node.frame.y = Math.floor(frame.y + padding);
1819  node.frame.width = Math.ceil(getV);
1820  node.frame.height = Math.floor(frame.height - padding * 2);
1821}
1822
1823export function mem(
1824  list: Array<unknown>,
1825  memFilter: Array<unknown>,
1826  startNS: number,
1827  endNS: number,
1828  totalNS: number,
1829  frame: Rect,
1830  use: boolean
1831): void {
1832  if (use && memFilter.length > 0) {
1833    for (let i = 0, len = memFilter.length; i < len; i++) {
1834      if (
1835        //@ts-ignore
1836        (memFilter[i].startTime || 0) + (memFilter[i].duration || 0) > startNS &&
1837        //@ts-ignore
1838        (memFilter[i].startTime || 0) < endNS
1839      ) {
1840        //@ts-ignore
1841        setMemFrame(memFilter[i], 5, startNS, endNS, totalNS, frame);
1842      } else {
1843        //@ts-ignore
1844        memFilter[i].frame = undefined;
1845      }
1846    }
1847    return;
1848  }
1849  memFilter.length = 0;
1850  //@ts-ignore
1851  setMemFilter(list, memFilter, startNS, endNS, totalNS, frame);
1852}
1853
1854function setMemFilter(
1855  list: Array<ProcessMemStruct>,
1856  memFilter: Array<ProcessMemStruct>,
1857  startNS: number,
1858  endNS: number,
1859  totalNS: number,
1860  frame: Rect
1861): void {
1862  if (list) {
1863    for (let i = 0, len = list.length; i < len; i++) {
1864      let it = list[i];
1865      if ((it.startTime || 0) + (it.duration || 0) > startNS && (it.startTime || 0) < endNS) {
1866        setMemFrame(list[i], 5, startNS, endNS, totalNS, frame);
1867        if (
1868          i > 0 &&
1869          (list[i - 1].frame?.x || 0) === (list[i].frame?.x || 0) &&
1870          (list[i - 1].frame?.width || 0) === (list[i].frame?.width || 0)
1871        ) {
1872        } else {
1873          memFilter.push(list[i]);
1874        }
1875      }
1876    }
1877  }
1878}
1879
1880export function drawWakeUpList(
1881  wakeUpListContext: CanvasRenderingContext2D | undefined | null,
1882  wake: WakeupBean | undefined | null,
1883  startNS: number,
1884  endNS: number,
1885  totalNS: number,
1886  frame: Rect,
1887  wakeup: WakeupBean | undefined = undefined,
1888  currentCpu: number | undefined | null = undefined,
1889  noVerticalLine = false
1890): void {
1891  if (!wakeUpListContext) {
1892    return;
1893  }
1894  if (wake) {
1895    let x1 = Math.floor(ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame));
1896    wakeUpListContext.beginPath();
1897    wakeUpListContext.lineWidth = 2;
1898    wakeUpListContext.fillStyle = '#000000';
1899    if (x1 > 0 && x1 < frame.x + frame.width) {
1900      if (!noVerticalLine) {
1901        wakeUpListContext.moveTo(x1, frame.y);
1902        wakeUpListContext.lineTo(x1, frame.y + frame.height);
1903      }
1904      if (currentCpu === wake.cpu) {
1905        let centerY = Math.floor(frame.y + frame.height / 2);
1906        wakeUpListContext.moveTo(x1, centerY - 6);
1907        wakeUpListContext.lineTo(x1 + 4, centerY);
1908        wakeUpListContext.lineTo(x1, centerY + 6);
1909        wakeUpListContext.lineTo(x1 - 4, centerY);
1910        wakeUpListContext.lineTo(x1, centerY - 6);
1911        wakeUpListContext.fill();
1912      }
1913    }
1914    if (wakeup) {
1915      drawWakeUpListIfWakeUp(wakeUpListContext, wake, startNS, endNS, totalNS, frame, wakeup, x1);
1916    }
1917    wakeUpListContext.strokeStyle = '#000000';
1918    wakeUpListContext.stroke();
1919    wakeUpListContext.closePath();
1920  }
1921}
1922
1923function drawWakeUpListIfWakeUp(
1924  wakeUpListContext: CanvasRenderingContext2D,
1925  wake: WakeupBean,
1926  startNS: number,
1927  endNS: number,
1928  totalNS: number,
1929  frame: Rect,
1930  wakeup: WakeupBean,
1931  x1: number
1932): void {
1933  let x2 = Math.floor(ns2x(wakeup.ts || 0, startNS, endNS, totalNS, frame));
1934  let y = frame.y + frame.height - 10;
1935  wakeUpListContext.moveTo(x1, y);
1936  wakeUpListContext.lineTo(x2, y);
1937  wakeUpListContext.moveTo(x2, y - 25);
1938  wakeUpListContext.lineTo(x2, y + 5);
1939  let s = ns2s((wakeup.ts || 0) - (wake.wakeupTime || 0));
1940  let wakeUpListDistance = x2 - x1;
1941  if (wakeUpListDistance > 12) {
1942    wakeUpListContext.moveTo(x1, y);
1943    wakeUpListContext.lineTo(x1 + 6, y - 3);
1944    wakeUpListContext.moveTo(x1, y);
1945    wakeUpListContext.lineTo(x1 + 6, y + 3);
1946    wakeUpListContext.moveTo(x2, y);
1947    wakeUpListContext.lineTo(x2 - 6, y - 3);
1948    wakeUpListContext.moveTo(x2, y);
1949    wakeUpListContext.lineTo(x2 - 6, y + 3);
1950    let measure = wakeUpListContext.measureText(s);
1951    let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
1952    let xStart = x1 + Math.floor(wakeUpListDistance / 2 - measure.width / 2);
1953    if (wakeUpListDistance > measure.width + 4) {
1954      wakeUpListContext.fillStyle = '#ffffff';
1955      wakeUpListContext.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4);
1956      wakeUpListContext.font = '10px solid';
1957      wakeUpListContext.fillStyle = '#000000';
1958      wakeUpListContext.textBaseline = 'bottom';
1959      wakeUpListContext.fillText(s, xStart, y - 2);
1960    }
1961  }
1962}
1963interface SearchNode {
1964  symbolName?: string;
1965  children: SearchNode[];
1966  searchShow?: boolean;
1967  isSearch?: boolean;
1968  parent?: SearchNode;
1969}
1970
1971export function findSearchNode(data: unknown[], search: string, parentSearch: boolean): void {
1972  search = search.toLocaleLowerCase();
1973  data.forEach((nodeIt) => {
1974    const node = nodeIt as SearchNode;
1975    if ((node.symbolName && node.symbolName.toLocaleLowerCase().includes(search) && search !== '') || parentSearch) {
1976      node.searchShow = true;
1977      node.isSearch = node.symbolName !== undefined && node.symbolName.toLocaleLowerCase().includes(search);
1978      let parentNode = node.parent;
1979      while (parentNode && !parentNode.searchShow) {
1980        parentNode.searchShow = true;
1981        parentNode = parentNode.parent;
1982      }
1983    } else {
1984      node.searchShow = false;
1985      node.isSearch = false;
1986    }
1987    if (node.children.length > 0) {
1988      findSearchNode(node.children, search, node.searchShow);
1989    }
1990  });
1991}
1992