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