• 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
560  constructor(
561    rowEL: TraceRow<BaseStruct>,
562    x: number,
563    y: number,
564    ns: number,
565    offsetY: number,
566    isRight: boolean,
567    business: string
568  ) {
569    this.rowEL = rowEL;
570    this.x = x;
571    this.y = y;
572    this.ns = ns;
573    this.offsetY = offsetY;
574    this.isRight = isRight;
575    this.business = business;
576  }
577}
578
579export class BaseStruct {
580  translateY: number | undefined;
581  frame: Rect | undefined;
582  isHover: boolean = false;
583}
584
585export function drawLines(ctx: CanvasRenderingContext2D, xs: Array<number>, height: number, lineColor: string): void {
586  if (ctx) {
587    ctx.beginPath();
588    ctx.lineWidth = 1;
589    ctx.strokeStyle = lineColor || '#dadada';
590    xs?.forEach((it) => {
591      ctx.moveTo(Math.floor(it), 0);
592      ctx.lineTo(Math.floor(it), height);
593    });
594    ctx.stroke();
595    ctx.closePath();
596  }
597}
598
599export function drawFlagLine(
600  commonCtx: CanvasRenderingContext2D,
601  hoverFlag: Flag,
602  selectFlag: Flag,
603  startNS: number,
604  endNS: number,
605  totalNS: number,
606  frame: Rect,
607  slicesTime:
608    | {
609      startTime: number | null | undefined;
610      endTime: number | null | undefined;
611      color: string | null | undefined;
612    }
613    | undefined
614): void {
615  if (commonCtx) {
616    if (hoverFlag) {
617      setHoverFlag(hoverFlag, commonCtx, frame);
618    }
619    if (selectFlag) {
620      commonCtx.beginPath();
621      commonCtx.lineWidth = 2;
622      commonCtx.strokeStyle = selectFlag?.color || '#dadada';
623      selectFlag.x = ns2x(selectFlag.time, startNS, endNS, totalNS, frame);
624      commonCtx.moveTo(Math.floor(selectFlag.x), 0);
625      commonCtx.lineTo(Math.floor(selectFlag.x), frame.height);
626      commonCtx.stroke();
627      commonCtx.closePath();
628    }
629    if (slicesTime && slicesTime.startTime && slicesTime.endTime) {
630      commonCtx.beginPath();
631      commonCtx.lineWidth = 1;
632      commonCtx.strokeStyle = slicesTime.color || '#dadada';
633      let x1 = ns2x(slicesTime.startTime, startNS, endNS, totalNS, frame);
634      let x2 = ns2x(slicesTime.endTime, startNS, endNS, totalNS, frame);
635      commonCtx.moveTo(Math.floor(x1), 0);
636      commonCtx.lineTo(Math.floor(x1), frame.height);
637      commonCtx.moveTo(Math.floor(x2), 0);
638      commonCtx.lineTo(Math.floor(x2), frame.height);
639      commonCtx.stroke();
640      commonCtx.closePath();
641    }
642  }
643}
644
645export function drawFlagLineSegment(
646  ctx: CanvasRenderingContext2D | null | undefined,
647  hoverFlag: Flag | null | undefined,
648  selectFlag: Flag | null | undefined,
649  frame: Rect,
650  tse: TimerShaftElement
651): void {
652  if (ctx) {
653    setHoverFlag(hoverFlag, ctx, frame);
654    setSelectFlag(selectFlag, ctx, frame);
655    tse.sportRuler!.slicesTimeList.forEach((slicesTime) => {
656      if (slicesTime && slicesTime.startTime && slicesTime.endTime) {
657        ctx.beginPath();
658        ctx.lineWidth = 1;
659        ctx.strokeStyle = slicesTime.color || '#dadada';
660        let x1 = ns2x(
661          slicesTime.startTime,
662          TraceRow.range!.startNS,
663          TraceRow.range!.endNS,
664          TraceRow.range!.totalNS,
665          frame
666        );
667        let x2 = ns2x(
668          slicesTime.endTime,
669          TraceRow.range!.startNS,
670          TraceRow.range!.endNS,
671          TraceRow.range!.totalNS,
672          frame
673        );
674        // 划线逻辑
675        ctx.moveTo(Math.floor(x1), 0);
676        ctx.lineTo(Math.floor(x1), frame.height!); //左边的线
677        ctx.moveTo(Math.floor(x2), 0);
678        ctx.lineTo(Math.floor(x2), frame.height!); // 右边的线
679        ctx.stroke();
680        ctx.closePath();
681      }
682    });
683  }
684}
685
686function setHoverFlag(hoverFlag: Flag | null | undefined, ctx: CanvasRenderingContext2D, frame: Rect): void {
687  if (hoverFlag) {
688    ctx.beginPath();
689    ctx.lineWidth = 2;
690    ctx.strokeStyle = hoverFlag?.color || '#dadada';
691    ctx.moveTo(Math.floor(hoverFlag.x), 0);
692    ctx.lineTo(Math.floor(hoverFlag.x), frame.height);
693    ctx.stroke();
694    ctx.closePath();
695  }
696}
697
698function setSelectFlag(selectFlag: Flag | null | undefined, ctx: CanvasRenderingContext2D, frame: Rect): void {
699  if (selectFlag) {
700    ctx.beginPath();
701    ctx.lineWidth = 2;
702    ctx.strokeStyle = selectFlag?.color || '#dadada';
703    selectFlag.x = ns2x(
704      selectFlag.time,
705      TraceRow.range!.startNS,
706      TraceRow.range!.endNS,
707      TraceRow.range!.totalNS,
708      frame
709    );
710    ctx.moveTo(Math.floor(selectFlag.x), 0);
711    ctx.lineTo(Math.floor(selectFlag.x), frame.height);
712    ctx.stroke();
713    ctx.closePath();
714  }
715}
716
717export function drawLogsLineSegment(
718  ctx: CanvasRenderingContext2D | undefined | null,
719  systemLogFlag: Flag | undefined | null,
720  frame: {
721    x: number;
722    y: number;
723    width: number | undefined;
724    height: number | undefined;
725  },
726  timerShaftEl: TimerShaftElement
727): void {
728  timerShaftEl.sportRuler?.draw();
729  if (systemLogFlag) {
730    if (ctx) {
731      ctx.beginPath();
732      ctx.lineWidth = 2;
733      ctx.strokeStyle = systemLogFlag?.color || '#dadada';
734      ctx.moveTo(Math.floor(systemLogFlag.x), 0);
735      ctx.lineTo(Math.floor(systemLogFlag.x), frame.height || 0);
736      ctx.stroke();
737      ctx.closePath();
738    }
739    if (timerShaftEl.ctx) {
740      let timeText = `| ${ns2Timestamp(systemLogFlag.time)}`;
741      let textPointX = systemLogFlag.x;
742      let textMetrics = timerShaftEl.ctx.measureText(timeText);
743      if (timerShaftEl.ctx.canvas.width - systemLogFlag.x <= textMetrics.width) {
744        textPointX = systemLogFlag.x - textMetrics.width;
745        timeText = `${ns2Timestamp(systemLogFlag.time)} |`;
746      }
747      let locationY = 120;
748      timerShaftEl.ctx.beginPath();
749      timerShaftEl.ctx.lineWidth = 0;
750      timerShaftEl.ctx.fillStyle = '#FFFFFF';
751      let textHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent;
752      timerShaftEl.ctx.fillRect(textPointX, locationY - textHeight, textMetrics.width, textHeight);
753      timerShaftEl.ctx.lineWidth = 2;
754      timerShaftEl.ctx.fillStyle = systemLogFlag?.color || '#dadada';
755      timerShaftEl.ctx.fillText(timeText, textPointX, locationY);
756      timerShaftEl.ctx.stroke();
757      timerShaftEl.ctx.closePath();
758    }
759  }
760}
761
762interface SelectionParams {
763  isRangeSelect: boolean;
764  rangeSelectObject?: RangeSelectStruct;
765  startNS: number;
766  endNS: number;
767  totalNS: number;
768  frame: Rect;
769}
770
771export function drawSelection(ctx: CanvasRenderingContext2D, params: unknown): void {
772  const param = params as SelectionParams;
773  if (param.isRangeSelect && param.rangeSelectObject) {
774    param.rangeSelectObject!.startX = Math.floor(
775      ns2x(param.rangeSelectObject!.startNS!, param.startNS, param.endNS, param.totalNS, param.frame)
776    );
777    param.rangeSelectObject!.endX = Math.floor(
778      ns2x(param.rangeSelectObject!.endNS!, param.startNS, param.endNS, param.totalNS, param.frame)
779    );
780    if (ctx) {
781      ctx.globalAlpha = 0.5;
782      ctx.fillStyle = '#666666';
783      ctx.fillRect(
784        param.rangeSelectObject!.startX!,
785        param.frame.y,
786        param.rangeSelectObject!.endX! - param.rangeSelectObject!.startX!,
787        param.frame.height
788      );
789      ctx.globalAlpha = 1;
790    }
791  }
792}
793
794// draw range select
795export function drawSelectionRange(context: CanvasRenderingContext2D, params: unknown): void {
796  const param = params as TraceRow<BaseStruct>;
797  if (param.rangeSelect && TraceRow.rangeSelectObject) {
798    setStartXEndX(param);
799    if (context) {
800      context.globalAlpha = 0.5;
801      context.fillStyle = '#666666';
802      context.fillRect(
803        TraceRow.rangeSelectObject!.startX!,
804        param.frame.y,
805        TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!,
806        param.frame.height
807      );
808      context.globalAlpha = 1;
809    }
810    // 绘制线程中方法平均帧率的箭头指示线条
811    if (param.avgRateTxt && param.frameRateList && param.frameRateList.length) {
812      drawAvgFrameRate(param.frameRateList, context, param);
813    }
814  }
815}
816
817function setStartXEndX(params: TraceRow<BaseStruct>): void {
818  TraceRow.rangeSelectObject!.startX = Math.floor(
819    ns2x(
820      TraceRow.rangeSelectObject!.startNS!,
821      TraceRow.range?.startNS ?? 0,
822      TraceRow.range?.endNS ?? 0,
823      TraceRow.range?.totalNS ?? 0,
824      params.frame
825    )
826  );
827  TraceRow.rangeSelectObject!.endX = Math.floor(
828    ns2x(
829      TraceRow.rangeSelectObject!.endNS!,
830      TraceRow.range?.startNS ?? 0,
831      TraceRow.range?.endNS ?? 0,
832      TraceRow.range?.totalNS ?? 0,
833      params.frame
834    )
835  );
836}
837
838function setAvgRateStartXEndX(rateList: number[], params: TraceRow<BaseStruct>): number[] {
839  let avgRateStartX = Math.floor(
840    ns2x(
841      rateList[0]!,
842      TraceRow.range?.startNS ?? 0,
843      TraceRow.range?.endNS ?? 0,
844      TraceRow.range?.totalNS ?? 0,
845      params.frame
846    )
847  );
848  let avgRateEndX = Math.floor(
849    ns2x(
850      rateList[rateList.length - 1]!,
851      TraceRow.range?.startNS ?? 0,
852      TraceRow.range?.endNS ?? 0,
853      TraceRow.range?.totalNS ?? 0,
854      params.frame
855    )
856  );
857  return [avgRateStartX, avgRateEndX];
858}
859
860function setTextXY(rateList: number[], params: TraceRow<BaseStruct>, textWidth: number): number[] {
861  let textX =
862    Math.floor(
863      ns2x(
864        (rateList[0]! + rateList[rateList.length - 1]!) / 2,
865        TraceRow.range?.startNS ?? 0,
866        TraceRow.range?.endNS ?? 0,
867        TraceRow.range?.totalNS ?? 0,
868        params.frame
869      )
870    ) -
871    textWidth / 2; // @ts-ignore
872  let textY = params.frame.y + 25;
873  return [textX, textY];
874}
875
876// 转换起始点坐标
877function changeFrameRatePoint(arrList: Array<number>, selectParams: TraceRow<BaseStruct>): number[] {
878  let avgRateStartX = Math.floor(
879    ns2x(
880      arrList[0]!,
881      TraceRow.range?.startNS ?? 0,
882      TraceRow.range?.endNS ?? 0,
883      TraceRow.range?.totalNS ?? 0,
884      selectParams.frame
885    )
886  ); // 起始坐标
887  let avgRateEndX = Math.floor(
888    ns2x(
889      arrList[arrList.length - 1]!,
890      TraceRow.range?.startNS ?? 0,
891      TraceRow.range?.endNS ?? 0,
892      TraceRow.range?.totalNS ?? 0,
893      selectParams.frame
894    )
895  ); // 结束坐标
896  return [avgRateStartX, avgRateEndX];
897}
898
899// 处理文字坐标
900function handleTextCoordinate(arrList: Array<number>, selectParams: TraceRow<BaseStruct>, textWidth: number): number[] {
901  const TEXT_WIDTH_HALF = 2;
902  let textX = Math.floor(
903    ns2x(
904      (arrList[0]! + arrList[arrList.length - 1]!) / 2,
905      TraceRow.range?.startNS ?? 0,
906      TraceRow.range?.endNS ?? 0,
907      TraceRow.range?.totalNS ?? 0,
908      selectParams.frame
909    )
910  ); //根据帧率范围的中间值转换文本的起始x坐标
911  textX = textX <= textWidth / TEXT_WIDTH_HALF ? textX : textX - textWidth / TEXT_WIDTH_HALF; // @ts-ignore
912  let textY = selectParams.frame.y + 11;
913  if (selectParams.avgRateTxt?.includes('HitchTime')) {
914    // @ts-ignore
915    textY = selectParams.frame.y + 11;
916  } else {
917    // 展开时显示在第二行,折叠显示第一行
918    if (selectParams.funcExpand) {
919      // @ts-ignore
920      textY = selectParams.frame.y + 29;
921    } else {
922      // @ts-ignore
923      textY = selectParams.frame.y + 11;
924    }
925  }
926  return [textX, textY];
927}
928
929// 绘制平均帧率箭头指示线条
930export function drawAvgFrameRate(
931  arrList: Array<number>,
932  ctx: CanvasRenderingContext2D,
933  selectParams: TraceRow<BaseStruct>
934): void {
935  let rateList: Array<number> = [...new Set(arrList)];
936  let startX = changeFrameRatePoint(rateList, selectParams)[0];
937  let endX = changeFrameRatePoint(rateList, selectParams)[1];
938  const textWidth = ctx.measureText(selectParams.avgRateTxt!).width;
939
940  const textHeight = 25;
941  const padding = 5;
942  let textX = handleTextCoordinate(rateList, selectParams, textWidth)[0];
943  let textY = handleTextCoordinate(rateList, selectParams, textWidth)[1];
944  //左移到边界,不画线和文字
945  startX = startX <= 0 ? -100 : startX;
946  endX = endX <= 0 ? -100 : endX;
947  textX = textX <= 0 ? -200 : textX;
948  //右移到边界,不画线和文字
949  const ADD_DISTANCE = 100;
950  textX = textX + textWidth / 2 >= selectParams.frame.width ?
951    selectParams.frame.width + ADD_DISTANCE : textX;
952  startX = startX >= selectParams.frame.width ?
953    selectParams.frame.width + ADD_DISTANCE : startX;
954  endX = endX >= selectParams.frame.width ?
955    selectParams.frame.width + ADD_DISTANCE : endX;
956
957  ctx.lineWidth = 2;
958  ctx.strokeStyle = 'yellow';
959  ctx.beginPath();
960  ctx.moveTo(startX, textY);
961  ctx.lineTo(endX, textY);
962  ctx.stroke();
963
964  const arrowSize = 5.5;
965  const arrowHead = (x: number, y: number, direction: 'left' | 'right'): void => {
966    ctx.beginPath();
967    const headX = x + (direction === 'left' ? arrowSize : -arrowSize);
968    const headY = y - arrowSize / 2;
969    ctx.moveTo(x, y);
970    ctx.lineTo(headX, headY);
971    ctx.lineTo(headX, y + arrowSize);
972    ctx.closePath();
973    ctx.fillStyle = 'yellow';
974    ctx.fill();
975  };
976  arrowHead(startX, textY - 1, 'left');
977  arrowHead(endX, textY - 1, 'right');
978
979  const TEXT_RECT_PADDING = 2;
980  ctx.fillStyle = 'red';
981  ctx.fillRect(
982    textX - padding,
983    textY - textHeight / TEXT_RECT_PADDING + padding,
984    textWidth + padding * TEXT_RECT_PADDING,
985    textHeight - padding * TEXT_RECT_PADDING
986  );
987
988  ctx.fillStyle = 'white';
989  ctx.fillText(selectParams.avgRateTxt!, textX, textY + 4);
990}
991
992function drawAvgFrameRateArrow(
993  ctx: CanvasRenderingContext2D,
994  textX: number,
995  textY: number,
996  textWidth: number,
997  startX: number,
998  endX: number,
999  avgFrameRate: string
1000): void {
1001  const textHeight = 25;
1002  const padding = 5;
1003  const TEXT_RECT_PADDING = 2;
1004  ctx.fillStyle = 'red';
1005  ctx.fillRect(
1006    textX - padding,
1007    textY - textHeight + padding,
1008    textWidth + padding * TEXT_RECT_PADDING,
1009    textHeight - padding * TEXT_RECT_PADDING
1010  );
1011  ctx.lineWidth = 2;
1012  ctx.strokeStyle = 'yellow';
1013  ctx.beginPath();
1014  ctx.moveTo(startX, textY);
1015  ctx.lineTo(endX, textY);
1016  ctx.stroke();
1017  arrowHead(ctx, startX, textY - 1, 'left');
1018  arrowHead(ctx, endX, textY - 1, 'right');
1019  ctx.fillStyle = 'white';
1020  ctx.fillText(avgFrameRate, textX, textY - 8);
1021}
1022
1023const arrowSize = 5.5;
1024const arrowHead = (ctx: CanvasRenderingContext2D, x: number, y: number, direction: 'left' | 'right'): void => {
1025  ctx.beginPath();
1026  const headX = x + (direction === 'left' ? arrowSize : -arrowSize);
1027  const headY = y - arrowSize / 2;
1028  ctx.moveTo(x, y);
1029  ctx.lineTo(headX, headY);
1030  ctx.lineTo(headX, y + arrowSize);
1031  ctx.closePath();
1032  ctx.fillStyle = 'yellow';
1033  ctx.fill();
1034};
1035
1036export function drawWakeUp(
1037  wakeUpContext: CanvasRenderingContext2D | undefined | null,
1038  wake: WakeupBean | undefined | null,
1039  startNS: number,
1040  endNS: number,
1041  totalNS: number,
1042  frame: Rect,
1043  selectCpuStruct: CpuStruct | undefined = undefined,
1044  wakeUpCurrentCpu: number | undefined = undefined,
1045  noVerticalLine = false
1046): void {
1047  if (wake && wakeUpContext) {
1048    let x1 = Math.floor(ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame));
1049    wakeUpContext.beginPath();
1050    wakeUpContext.lineWidth = 2;
1051    wakeUpContext.fillStyle = '#000000';
1052    if (x1 > 0 && x1 < frame.x + frame.width) {
1053      if (!noVerticalLine) {
1054        wakeUpContext.moveTo(x1, frame.y);
1055        wakeUpContext.lineTo(x1, frame.y + frame.height);
1056      }
1057      if (wakeUpCurrentCpu === wake.cpu) {
1058        let centerY = Math.floor(frame.y + frame.height / 2);
1059        wakeUpContext.moveTo(x1, centerY - 6);
1060        wakeUpContext.lineTo(x1 + 4, centerY);
1061        wakeUpContext.lineTo(x1, centerY + 6);
1062        wakeUpContext.lineTo(x1 - 4, centerY);
1063        wakeUpContext.lineTo(x1, centerY - 6);
1064        wakeUpContext.fill();
1065      }
1066    }
1067    if (selectCpuStruct) {
1068      drawWakeUpIfSelect(selectCpuStruct, startNS, endNS, totalNS, frame, wakeUpContext, wake, x1);
1069    }
1070    wakeUpContext.strokeStyle = '#000000';
1071    wakeUpContext.stroke();
1072    wakeUpContext.closePath();
1073  }
1074}
1075
1076function drawWakeUpIfSelect(
1077  selectCpuStruct: CpuStruct,
1078  startNS: number,
1079  endNS: number,
1080  totalNS: number,
1081  frame: Rect,
1082  wakeUpContext: CanvasRenderingContext2D,
1083  wake: unknown,
1084  x1: number
1085): void {
1086  let x2 = Math.floor(ns2x(selectCpuStruct.startTime || 0, startNS, endNS, totalNS, frame));
1087  let y = frame.y + frame.height - 10;
1088  wakeUpContext.moveTo(x1, y);
1089  wakeUpContext.lineTo(x2, y);
1090  //@ts-ignore
1091  let s = ns2s((selectCpuStruct.startTime || 0) - (wake.wakeupTime || 0));
1092  let distance = x2 - x1;
1093  if (distance > 12) {
1094    wakeUpContext.moveTo(x1, y);
1095    wakeUpContext.lineTo(x1 + 6, y - 3);
1096    wakeUpContext.moveTo(x1, y);
1097    wakeUpContext.lineTo(x1 + 6, y + 3);
1098    wakeUpContext.moveTo(x2, y);
1099    wakeUpContext.lineTo(x2 - 6, y - 3);
1100    wakeUpContext.moveTo(x2, y);
1101    wakeUpContext.lineTo(x2 - 6, y + 3);
1102    let measure = wakeUpContext.measureText(s);
1103    let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
1104    let xStart = x1 + Math.floor(distance / 2 - measure.width / 2);
1105    if (distance > measure.width + 4) {
1106      wakeUpContext.fillStyle = '#ffffff';
1107      wakeUpContext.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4);
1108      wakeUpContext.font = '10px solid';
1109      wakeUpContext.fillStyle = '#000000';
1110      wakeUpContext.textBaseline = 'bottom';
1111      wakeUpContext.fillText(s, xStart, y - 2);
1112    }
1113  }
1114}
1115
1116const wid = 5;
1117const linkLineColor = '#ff0000';
1118
1119export function drawLinkLines(
1120  context: CanvasRenderingContext2D,
1121  nodes: PairPoint[][],
1122  tm: TimerShaftElement,
1123  isFavorite: boolean,
1124  favoriteHeight: number
1125): void {
1126  let percentage =
1127    (tm.getRange()!.totalNS - Math.abs(tm.getRange()!.endNS - tm.getRange()!.startNS)) / tm.getRange()!.totalNS;
1128  let maxWidth = tm.getBoundingClientRect().width - 258;
1129  setLinkLinesNodes(nodes, isFavorite, favoriteHeight, maxWidth, context, percentage);
1130}
1131
1132function setLinkLinesNodes(
1133  nodes: PairPoint[][],
1134  isFav: boolean,
1135  favH: number,
1136  max: number,
1137  context: CanvasRenderingContext2D,
1138  perc: number
1139): void {
1140  for (let i = 0; i < nodes.length; i++) {
1141    let it = nodes[i];
1142    const traceRow0 = it[0].rowEL as TraceRow<BaseStruct>;
1143    const traceRow1 = it[1].rowEL as TraceRow<BaseStruct>;
1144    it[0].y = traceRow0.translateY + it[0].offsetY;
1145    it[1].y = traceRow1.translateY + it[1].offsetY;
1146    let newFirstNode = new PairPoint(
1147      traceRow0,
1148      it[0].x,
1149      it[0].y,
1150      it[0].ns,
1151      it[0].offsetY,
1152      it[0].isRight,
1153      it[0].business
1154    );
1155    let newSecondNode = new PairPoint(
1156      traceRow1,
1157      it[1].x,
1158      it[1].y,
1159      it[1].ns,
1160      it[1].offsetY,
1161      it[1].isRight,
1162      it[1].business
1163    );
1164    if (it[0].lineColor) {
1165      newFirstNode.lineColor = it[0].lineColor;
1166      newSecondNode.lineColor = it[0].lineColor;
1167    }
1168    if (it[0].rangeTime) {
1169      newFirstNode.rangeTime = it[0].rangeTime;
1170    }
1171    if (it[0].hidden) {
1172      continue;
1173    }
1174    if (isFav) {
1175      if (traceRow0.collect && traceRow1.collect) {
1176      } else if (!traceRow0.collect && !traceRow1.collect) {
1177        continue;
1178      } else {
1179        traceRow0.collect ? (newSecondNode.y = Math.max(it[1].y + favH, favH)) :
1180          (newFirstNode.y = Math.max(it[0].y + favH, favH));
1181      }
1182    } else {
1183      if (traceRow0.collect && traceRow1.collect) {
1184        continue;
1185      } else if (!traceRow0.collect && !traceRow1.collect) {
1186      } else {
1187        traceRow0.collect ? (newFirstNode.y = it[0].y - favH) : (newSecondNode.y = it[1].y - favH);
1188      }
1189    }
1190    drawLinesByType(it[0].lineType, newFirstNode, newSecondNode, max, context, perc);
1191  }
1192}
1193
1194function drawLinesByType(
1195  lineType: LineType | undefined,
1196  newFirstNode: PairPoint,
1197  newSecondNode: PairPoint,
1198  maxWidth: number,
1199  context: CanvasRenderingContext2D,
1200  percentage: number
1201): void {
1202  switch (lineType) {
1203    case LineType.brokenLine:
1204      drawBrokenLine([newFirstNode, newSecondNode], maxWidth, context);
1205      break;
1206    case LineType.bezierCurve:
1207      drawBezierCurve([newFirstNode, newSecondNode], maxWidth, context, percentage);
1208      break;
1209    case LineType.straightLine:
1210      drawStraightLine([newFirstNode, newSecondNode], maxWidth, context);
1211      break;
1212    default:
1213      drawBezierCurve([newFirstNode, newSecondNode], maxWidth, context, percentage);
1214  }
1215}
1216
1217function drawBezierCurve(
1218  it: PairPoint[],
1219  maxWidth: number,
1220  context: CanvasRenderingContext2D,
1221  percentage: number
1222): void {
1223  let bezierCurveStart = it[0].x > it[1].x ? it[1] : it[0];
1224  let bezierCurveEnd = it[0].x > it[1].x ? it[0] : it[1];
1225  if (bezierCurveStart && bezierCurveEnd) {
1226    //左移到边界,不画线
1227    if (bezierCurveStart.x <= 0) {
1228      bezierCurveStart.x = -100;
1229    }
1230    if (bezierCurveEnd.x <= 0) {
1231      bezierCurveEnd.x = -100;
1232    }
1233    //右移到边界,不画线
1234    if (bezierCurveStart.x >= maxWidth) {
1235      bezierCurveStart.x = maxWidth + 100;
1236    }
1237    if (bezierCurveEnd.x >= maxWidth) {
1238      bezierCurveEnd.x = maxWidth + 100;
1239    }
1240    drawBezierCurveContext(context, bezierCurveStart, bezierCurveEnd, percentage);
1241  }
1242}
1243
1244function drawBezierCurveContext(
1245  context: CanvasRenderingContext2D,
1246  bezierCurveStart: PairPoint,
1247  bezierCurveEnd: PairPoint,
1248  percentage: number
1249): void {
1250  context.beginPath();
1251  context.lineWidth = 2;
1252  context.fillStyle = linkLineColor;
1253  context.strokeStyle = linkLineColor;
1254  let x0 = bezierCurveStart.x ?? 0;
1255  let y0 = bezierCurveStart.y ?? 0;
1256  let x3 = bezierCurveEnd.x ?? 0;
1257  let y3 = bezierCurveEnd.y ?? 0;
1258  let x2 = bezierCurveEnd.isRight ? x3 - 100 * percentage : x3 + 100 * percentage;
1259  let y2 = y3 - 40 * percentage;
1260  let x1 = bezierCurveStart.isRight ? x0 - 100 * percentage : x0 + 100 * percentage;
1261  let y1 = y0 + 40 * percentage;
1262  if (!bezierCurveStart.isRight) {
1263    x0 -= 5;
1264  }
1265  context.moveTo(x0, y0);
1266  if (bezierCurveStart.isRight) {
1267    context.lineTo(x0 - wid, y0 + wid);
1268    context.moveTo(x0, y0);
1269    context.lineTo(x0 - wid, y0 - wid);
1270  } else {
1271    context.lineTo(x0 + wid, y0 + wid);
1272    context.moveTo(x0, y0);
1273    context.lineTo(x0 + wid, y0 - wid);
1274  }
1275  context.moveTo(x0, y0);
1276  context.bezierCurveTo(x1, y1, x2, y2, x3, y3);
1277  context.moveTo(x3, y3);
1278  if (bezierCurveEnd.isRight) {
1279    context.lineTo(x3 - wid, y3 + wid);
1280    context.moveTo(x3, y3);
1281    context.lineTo(x3 - wid, y3 - wid);
1282  } else {
1283    context.lineTo(x3 + wid, y3 + wid);
1284    context.moveTo(x3, y3);
1285    context.lineTo(x3 + wid, y3 - wid);
1286  }
1287  context.moveTo(x3, y3);
1288  context.stroke();
1289  context.closePath();
1290}
1291
1292function drawStraightLine(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D): void {
1293  let startPoint = it[0].x > it[1].x ? it[1] : it[0];
1294  let endPoint = it[0].x > it[1].x ? it[0] : it[1];
1295  let arrowSize = 8;
1296  if (startPoint && endPoint) {
1297    //左移到边界,不画线
1298    if (startPoint.x <= 0) {
1299      startPoint.x = -100;
1300    }
1301    if (endPoint.x <= 0) {
1302      endPoint.x = -100;
1303    }
1304    //右移到边界,不画线
1305    if (startPoint.x >= maxWidth) {
1306      startPoint.x = maxWidth + 100;
1307    }
1308    if (endPoint.x >= maxWidth) {
1309      endPoint.x = maxWidth + 100;
1310    }
1311    drawArrow(context, startPoint, endPoint, arrowSize);
1312  }
1313}
1314
1315function drawArrow(
1316  context: CanvasRenderingContext2D,
1317  startPoint: PairPoint,
1318  endPoint: PairPoint,
1319  arrowSize: number
1320): void {
1321  context.beginPath();
1322  context.lineWidth = 2;
1323  context.strokeStyle = '#0000FF';
1324  context.moveTo(startPoint.x, startPoint.y);
1325  context.lineTo(endPoint.x, endPoint.y);
1326  // 绘制箭头
1327  let arrow = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
1328  context.moveTo(endPoint.x, endPoint.y);
1329  context.lineTo(
1330    endPoint.x - arrowSize * Math.cos(arrow - Math.PI / 6),
1331    endPoint.y - arrowSize * Math.sin(arrow - Math.PI / 6)
1332  );
1333  context.moveTo(endPoint.x, endPoint.y);
1334  context.lineTo(
1335    endPoint.x - arrowSize * Math.cos(arrow + Math.PI / 6),
1336    endPoint.y - arrowSize * Math.sin(arrow + Math.PI / 6)
1337  );
1338  // 绘制另一端箭头
1339  arrow = Math.atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
1340  context.moveTo(startPoint.x, startPoint.y);
1341  context.lineTo(
1342    startPoint.x - arrowSize * Math.cos(arrow - Math.PI / 6),
1343    startPoint.y - arrowSize * Math.sin(arrow - Math.PI / 6)
1344  );
1345  context.moveTo(startPoint.x, startPoint.y);
1346  context.lineTo(
1347    startPoint.x - arrowSize * Math.cos(arrow + Math.PI / 6),
1348    startPoint.y - arrowSize * Math.sin(arrow + Math.PI / 6)
1349  );
1350  context.stroke();
1351  context.closePath();
1352}
1353
1354function drawBrokenLine(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D): void {
1355  let brokenLineStart = it[0].x > it[1].x ? it[1] : it[0];
1356  let brokenLineEnd = it[0].x > it[1].x ? it[0] : it[1];
1357  if (brokenLineStart && brokenLineEnd) {
1358    if (brokenLineStart.x <= 0) {
1359      brokenLineStart.x = -100;
1360    }
1361    if (brokenLineEnd.x <= 0) {
1362      brokenLineEnd.x = -100;
1363    }
1364    if (brokenLineStart.x >= maxWidth) {
1365      brokenLineStart.x = maxWidth + 100;
1366    }
1367    if (brokenLineEnd.x >= maxWidth) {
1368      brokenLineEnd.x = maxWidth + 100;
1369    }
1370    drawBrokenLineContext(context, brokenLineStart, brokenLineEnd);
1371  }
1372}
1373
1374function drawBrokenLineContext(
1375  context: CanvasRenderingContext2D,
1376  brokenLineStart: PairPoint,
1377  brokenLineEnd: PairPoint
1378): void {
1379  context.beginPath();
1380  context.lineWidth = 2;
1381  context.fillStyle = brokenLineStart.lineColor ? brokenLineStart.lineColor : '#46B1E3';
1382  context.strokeStyle = brokenLineStart.lineColor ? brokenLineStart.lineColor : '#46B1E3';
1383  let x0 = brokenLineStart.x ?? 0;
1384  let y0 = brokenLineStart.y ?? 0;
1385  let y2 = brokenLineEnd.y ?? 0;
1386  let x2 = brokenLineEnd.x ?? 0;
1387  let x1;
1388  let y1;
1389  let leftEndpointX;
1390  let leftEndpointY;
1391  let rightEndpointX;
1392  let rightEndpointY;
1393  if (brokenLineStart.y < brokenLineEnd.y) {
1394    x1 = brokenLineStart.x ?? 0;
1395    y1 = brokenLineEnd.y ?? 0;
1396    leftEndpointX = x2 - wid;
1397    leftEndpointY = y2 - wid;
1398    rightEndpointX = x2 - wid;
1399    rightEndpointY = y2 + wid;
1400  } else {
1401    // @ts-ignore
1402    x2 = brokenLineEnd.x - wid ?? 0;
1403    // @ts-ignore
1404    x1 = brokenLineEnd.x - wid ?? 0;
1405    y1 = brokenLineStart.y ?? 0;
1406    leftEndpointX = x2 - wid;
1407    leftEndpointY = y2 + wid;
1408    rightEndpointX = x2 + wid;
1409    rightEndpointY = y2 + wid;
1410  }
1411  x1 = drawDistributedLineTime(brokenLineStart.business, brokenLineStart.rangeTime!, [x0, y0, x1, y1, x2, y2], context);
1412  context.moveTo(x0 - 2, y0);
1413  context.lineTo(x1, y1);
1414  context.lineTo(x2, y2);
1415  context.stroke();
1416  context.closePath();
1417  context.beginPath();
1418  context.lineWidth = 2;
1419  context.fillStyle = brokenLineStart.lineColor ? brokenLineStart.lineColor : '#46B1E3';
1420  context.strokeStyle = brokenLineStart.lineColor ? brokenLineStart.lineColor : '#46B1E3';
1421  context.moveTo(x2, y2);
1422  context.lineTo(leftEndpointX, leftEndpointY);
1423  context.lineTo(rightEndpointX, rightEndpointY);
1424  context.lineTo(x2, y2);
1425  context.fill();
1426  context.closePath();
1427}
1428
1429let loadingText = 'Loading...';
1430let loadingTextWidth = 0;
1431let loadingBackground = '#f1f1f1';
1432let loadingFont = 'bold 11pt Arial';
1433let loadingFontColor = '#696969';
1434
1435function drawDistributedLineTime(
1436  business: string,
1437  rangeTime: string,
1438  [x0, y0, x1, y1, x2, y2]: [number, number, number, number, number, number],
1439  context: CanvasRenderingContext2D
1440): number {
1441  if (business === 'distributed') {
1442    if (y0 === y1) {
1443      drawString(context, rangeTime, 0,
1444        new Rect(x0, y0 + 2, x1 - x0, 12), { textMetricsWidth: undefined });
1445    } else {
1446      drawString(context, rangeTime, 0,
1447        new Rect(x1, y1 + 2, x2 - x1, 12), { textMetricsWidth: undefined });
1448      x1 = x1 - 2;
1449    }
1450  }
1451  return x1;
1452}
1453
1454export function drawLoadingFrame(
1455  ctx: CanvasRenderingContext2D,
1456  list: Array<unknown>,
1457  traceRow: unknown,
1458  sort: boolean = false
1459): void {
1460  const row = traceRow as TraceRow<BaseStruct>;
1461  ctx.beginPath();
1462  ctx.clearRect(0, 0, row.frame.width, row.frame.height);
1463  drawLines(ctx, TraceRow.range?.xs || [], row.frame.height, '#dadada');
1464  drawVSync(ctx, row.frame.width, row.frame.height);
1465  if (row.loadingFrame) {
1466    if (loadingTextWidth === 0) {
1467      loadingTextWidth = ctx.measureText(loadingText).width;
1468    }
1469    let firstPx = nsx(row.loadingPin1, row.frame.width);
1470    let lastPx = nsx(row.loadingPin2, row.frame.width);
1471    ctx.fillStyle = loadingBackground;
1472    ctx.fillRect(0, 1, firstPx, row.frame.height - 2);
1473    ctx.fillRect(lastPx, 1, row.frame.width - lastPx, row.frame.height - 2);
1474    ctx.fillStyle = loadingFontColor;
1475    if (firstPx > loadingTextWidth) {
1476      ctx.fillText(loadingText, (firstPx - loadingTextWidth) / 2, row.frame.height / 2);
1477    }
1478    if (row.frame.width - lastPx > loadingTextWidth) {
1479      ctx.fillText(loadingText, lastPx + (row.frame.width - lastPx) / 2 - loadingTextWidth / 2, row.frame.height / 2);
1480    }
1481  }
1482  ctx.closePath();
1483}
1484
1485export function drawString(
1486  ctx: CanvasRenderingContext2D,
1487  str: string,
1488  textPadding: number,
1489  frame: Rect,
1490  data: unknown
1491): void {
1492  //@ts-ignore
1493  if (data.textMetricsWidth === undefined) {
1494    //@ts-ignore
1495    data.textMetricsWidth = ctx.measureText(str);
1496  }
1497  //@ts-ignore
1498  const textMetricsWidth = (data.textMetricsWidth as TextMetrics).width;
1499  const yPos = 1.5;
1500  let charWidth = Math.round(textMetricsWidth / str.length);
1501  let fillTextWidth = frame.width - textPadding * 2;
1502  if (textMetricsWidth < fillTextWidth) {
1503    let x2 = Math.floor(frame.width / 2 - textMetricsWidth / 2 + frame.x + textPadding);
1504    ctx.fillText(str, x2, Math.floor(frame.y + frame.height / yPos), fillTextWidth);
1505  } else {
1506    if (fillTextWidth >= charWidth) {
1507      let chatNum = fillTextWidth / charWidth;
1508      let x1 = frame.x + textPadding;
1509
1510      if (chatNum < 2) {
1511        ctx.fillText(str.substring(0, 1), x1, Math.floor(frame.y + frame.height / yPos), fillTextWidth);
1512      } else {
1513        ctx.fillText(
1514          `${str.substring(0, chatNum - 1)}...`,
1515          x1,
1516          Math.floor(frame.y + frame.height / yPos),
1517          fillTextWidth
1518        );
1519      }
1520    }
1521  }
1522}
1523
1524export function drawFunString(
1525  ctx: CanvasRenderingContext2D,
1526  str: string,
1527  textPadding: number,
1528  frame: Rect,
1529  data: FuncStruct
1530): void {
1531  if (data.textMetricsWidth === undefined) {
1532    data.textMetricsWidth = ctx.measureText(str).width;
1533  }
1534  let charWidth = Math.round(data.textMetricsWidth / str.length);
1535  let fillTextWidth = frame.width - textPadding * 2;
1536  if (data.textMetricsWidth < fillTextWidth) {
1537    let x2 = Math.floor(frame.width / 2 - data.textMetricsWidth / 2 + frame.x + textPadding);
1538    ctx.fillText(str, x2, Math.floor(data.frame!.height * (data.depth! + 0.5) + 3), fillTextWidth);
1539  } else {
1540    if (fillTextWidth >= charWidth) {
1541      let chatNum = fillTextWidth / charWidth;
1542      let x1 = frame.x + textPadding;
1543      if (chatNum < 2) {
1544        ctx.fillText(str.substring(0, 1), x1, Math.floor(data.frame!.height * (data.depth! + 0.5) + 3), fillTextWidth);
1545      } else {
1546        ctx.fillText(
1547          `${str.substring(0, chatNum - 1)}...`,
1548          x1,
1549          Math.floor(data.frame!.height * (data.depth! + 0.5) + 3),
1550          fillTextWidth
1551        );
1552      }
1553    }
1554  }
1555}
1556
1557export function hiPerf(
1558  arr: Array<HiPerfStruct>,
1559  arr2: Array<HiPerfStruct>,
1560  res: Array<HiPerfStruct>,
1561  startNS: number,
1562  endNS: number,
1563  frame: Rect,
1564  groupBy10MS: boolean,
1565  use: boolean
1566): void {
1567  if (use && res.length > 0) {
1568    setFrameByRes(res, startNS, endNS, frame);
1569    return;
1570  }
1571  res.length = 0;
1572  if (arr) {
1573    setFrameByArr(arr, arr2, res, startNS, endNS, frame, groupBy10MS);
1574  }
1575}
1576
1577function setFrameByRes(res: Array<HiPerfStruct>, startNS: number, endNS: number, frame: Rect): void {
1578  let pns = (endNS - startNS) / frame.width;
1579  let y = frame.y;
1580  for (let i = 0; i < res.length; i++) {
1581    let item = res[i];
1582    if ((item.startNS || 0) + (item.dur || 0) > startNS && (item.startNS || 0) < endNS) {
1583      if (!item.frame) {
1584        item.frame = new Rect(0, 0, 0, 0);
1585        item.frame.y = y;
1586      }
1587      item.frame.height = item.height!;
1588      HiPerfStruct.setFrame(item, pns, startNS, endNS, frame);
1589    } else {
1590      item.frame = undefined;
1591    }
1592  }
1593}
1594
1595function setFrameByArr(
1596  arr: Array<HiPerfStruct>,
1597  arr2: Array<HiPerfStruct>,
1598  res: Array<HiPerfStruct>,
1599  startNS: number,
1600  endNS: number,
1601  frame: Rect,
1602  groupBy10MS: boolean
1603): void {
1604  let list = groupBy10MS ? arr2 : arr;
1605  let pns = (endNS - startNS) / frame.width;
1606  let y = frame.y;
1607  for (let i = 0, len = list.length; i < len; i++) {
1608    let it = list[i];
1609    if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
1610      if (!list[i].frame) {
1611        list[i].frame = new Rect(0, 0, 0, 0);
1612        list[i].frame!.y = y;
1613      }
1614      list[i].frame!.height = it.height!;
1615      HiPerfStruct.setFrame(list[i], pns, startNS, endNS, frame);
1616      setResultArr(groupBy10MS, list, i, res);
1617    }
1618  }
1619}
1620
1621function setResultArr(groupBy10MS: boolean, list: Array<HiPerfStruct>, i: number, res: Array<HiPerfStruct>): void {
1622  const itemI = list[i];
1623  const itemBeforeI = list[i - 1];
1624  if (itemI.frame && itemBeforeI.frame) {
1625    if (groupBy10MS) {
1626      let flag: boolean =
1627        i > 0 &&
1628        (itemBeforeI.frame.x || 0) === (itemI.frame.x || 0) &&
1629        (itemBeforeI.frame.width || 0) === (itemI.frame.width || 0) &&
1630        (itemBeforeI.frame.height || 0) === (itemI.frame.height || 0);
1631      if (!flag) {
1632        res.push(itemI);
1633      }
1634    } else {
1635      if (!(i > 0 && Math.abs((itemBeforeI.frame.x || 0) - (itemI.frame.x || 0)) < 4)) {
1636        res.push(itemI);
1637      }
1638    }
1639  }
1640}
1641
1642export function hiPerf2(filter: Array<HiPerfStruct>, startNS: number, endNS: number, frame: Rect): void {
1643  if (filter.length > 0) {
1644    let pns = (endNS - startNS) / frame.width;
1645    let y = frame.y;
1646    for (let i = 0; i < filter.length; i++) {
1647      let it = filter[i];
1648      if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
1649        if (!it.frame) {
1650          it.frame = new Rect(0, 0, 0, 0);
1651          it.frame.y = y;
1652        }
1653        it.frame.height = it.height!;
1654        HiPerfStruct.setFrame(it, pns, startNS, endNS, frame);
1655      } else {
1656        it.frame = undefined;
1657      }
1658    }
1659    return;
1660  }
1661}
1662
1663export class HiPerfStruct extends BaseStruct {
1664  static hoverStruct: HiPerfStruct | undefined;
1665  static selectStruct: HiPerfStruct | undefined;
1666  static bottomFindCount: number = 0;
1667  id: number | undefined;
1668  callchain_id: number | undefined;
1669  timestamp: number | undefined;
1670  thread_id: number | undefined;
1671  event_count: number | undefined;
1672  event_type_id: number | undefined;
1673  cpu_id: number | undefined;
1674  thread_state: string | undefined;
1675  startNS: number | undefined;
1676  endNS: number | undefined;
1677  dur: number | undefined;
1678  height: number | undefined;
1679  eventCount: number | undefined;
1680  sampleCount: number | undefined;
1681
1682  static drawRoundRectPath(cxt: Path2D, x: number, y: number, width: number, height: number, radius: number): void {
1683    cxt.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2);
1684    cxt.lineTo(x + radius, y + height);
1685    cxt.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI);
1686    cxt.lineTo(x, y + radius);
1687    cxt.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
1688    cxt.lineTo(x + width - radius, y);
1689    cxt.arc(x + width - radius, y + radius, radius, (Math.PI * 3) / 2, Math.PI * 2);
1690    cxt.lineTo(x + width, y + height - radius);
1691    cxt.moveTo(x + width / 3, y + height / 5);
1692    cxt.lineTo(x + width / 3, y + (height / 5) * 4);
1693    cxt.moveTo(x + width / 3, y + height / 5);
1694    cxt.bezierCurveTo(
1695      x + width / 3 + 7,
1696      y + height / 5 - 2,
1697      x + width / 3 + 7,
1698      y + height / 5 + 6,
1699      x + width / 3,
1700      y + height / 5 + 4
1701    );
1702  }
1703
1704  static draw(
1705    ctx: CanvasRenderingContext2D,
1706    normalPath: Path2D,
1707    specPath: Path2D,
1708    data: HiPerfStruct,
1709    groupBy10MS: boolean,
1710    textMetrics?: TextMetrics
1711  ): void {
1712    if (data.frame) {
1713      if (groupBy10MS) {
1714        let width = data.frame.width;
1715        normalPath.rect(data.frame.x, 40 - (data.height || 0), width, data.height || 0);
1716      } else {
1717        data.frame.width > 4 ? (data.frame.width = 4) : (data.frame.width = data.frame.width);
1718        let path = data.callchain_id === -1 ? specPath : normalPath;
1719        path.moveTo(data.frame.x + 7, 20);
1720        if (textMetrics) {
1721          ctx.fillText('��', data.frame.x - textMetrics!.width / 2, 26); //℗©®℗®��
1722        } else {
1723          HiPerfStruct.drawRoundRectPath(path, data.frame.x - 7, 20 - 7, 14, 14, 3);
1724        }
1725        path.moveTo(data.frame.x, 27);
1726        path.lineTo(data.frame.x, 33);
1727      }
1728    }
1729  }
1730
1731  static drawSpecialPath(ctx: CanvasRenderingContext2D, specPath: Path2D): void {
1732    ctx.strokeStyle = '#9fafc4';
1733    ctx.globalAlpha = 0.5;
1734    ctx.stroke(specPath);
1735    ctx.globalAlpha = 1;
1736  }
1737
1738  static setFrame(node: HiPerfStruct, pns: number, startNS: number, endNS: number, frame: Rect): void {
1739    if (!node.frame) {
1740      return;
1741    }
1742    if ((node.startNS || 0) < startNS) {
1743      node.frame.x = 0;
1744    } else {
1745      node.frame.x = Math.floor(((node.startNS || 0) - startNS) / pns);
1746    }
1747    if ((node.startNS || 0) + (node.dur || 0) > endNS) {
1748      node.frame.width = frame.width - node.frame.x;
1749    } else {
1750      node.frame.width = Math.ceil(((node.startNS || 0) + (node.dur || 0) - startNS) / pns - node.frame.x);
1751    }
1752    if (node.frame.width < 1) {
1753      node.frame.width = 1;
1754    }
1755  }
1756
1757  static groupBy10MS(
1758    groupArray: Array<HiPerfStruct>,
1759    intervalPerf: number,
1760    maxCpu?: number | undefined,
1761    usage?: boolean,
1762    event?: number
1763  ): Array<HiPerfStruct> {
1764    let maxEventCount = 0;
1765    let obj = filterGroupArray(groupArray, maxEventCount, usage, event);
1766    let arr = [];
1767    for (let aKey in obj) {
1768      let ns = parseInt(aKey);
1769      let height: number = 0;
1770      if (usage) {
1771        if (maxCpu !== undefined) {
1772          //@ts-ignore
1773          height = Math.floor((obj[aKey].sampleCount / (10 / intervalPerf) / maxCpu) * 40);
1774        } else {
1775          //@ts-ignore
1776          height = Math.floor((obj[aKey].sampleCount / (10 / intervalPerf)) * 40);
1777        }
1778      } else {
1779        //@ts-ignore
1780        height = Math.floor((obj[aKey].eventCount / maxEventCount) * 40);
1781      }
1782      arr.push({
1783        startNS: ns,
1784        dur: 10_000_000,
1785        //@ts-ignore
1786        eventCount: obj[aKey].eventCount,
1787        //@ts-ignore
1788        sampleCount: obj[aKey].sampleCount,
1789        height: height,
1790      });
1791    }
1792    return arr as HiPerfStruct[];
1793  }
1794}
1795
1796function filterGroupArray(
1797  groupArray: Array<HiPerfStruct>,
1798  maxEventCount: number,
1799  usage?: boolean,
1800  event?: number
1801): HiPerfStruct {
1802  const map = groupArray.map((it) => {
1803    //@ts-ignore
1804    it.timestamp_group = Math.trunc(it.startNS / 10_000_000) * 10_000_000;
1805    return it;
1806  });
1807  const reduce = map.reduce((pre: HiPerfStruct, current: HiPerfStruct) => {
1808    if (usage || current.event_type_id === event || event === -1) {
1809      //@ts-ignore
1810      if (pre[current.timestamp_group]) {
1811        //@ts-ignore
1812        pre[current.timestamp_group].sampleCount += 1;
1813        //@ts-ignore
1814        pre[current.timestamp_group].eventCount += current.event_count;
1815      } else {
1816        //@ts-ignore
1817        pre[current.timestamp_group] = {
1818          sampleCount: 1,
1819          eventCount: current.event_count,
1820        };
1821      }
1822      //@ts-ignore
1823      maxEventCount = Math.max(pre[current.timestamp_group].eventCount, maxEventCount);
1824    }
1825    return pre;
1826  }, new HiPerfStruct());
1827  return reduce;
1828}
1829
1830function setMemFrame(
1831  node: ProcessMemStruct,
1832  padding: number,
1833  startNS: number,
1834  endNS: number,
1835  totalNS: number,
1836  frame: Rect
1837): void {
1838  let x1: number;
1839  let x2: number;
1840  if ((node.startTime || 0) <= startNS) {
1841    x1 = 0;
1842  } else {
1843    x1 = ns2x(node.startTime || 0, startNS, endNS, totalNS, frame);
1844  }
1845  if ((node.startTime || 0) + (node.duration || 0) >= endNS) {
1846    x2 = frame.width;
1847  } else {
1848    x2 = ns2x((node.startTime || 0) + (node.duration || 0), startNS, endNS, totalNS, frame);
1849  }
1850  let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1;
1851  if (!node.frame) {
1852    node.frame = new Rect(0, 0, 0, 0);
1853  }
1854  node.frame.x = Math.floor(x1);
1855  node.frame.y = Math.floor(frame.y + padding);
1856  node.frame.width = Math.ceil(getV);
1857  node.frame.height = Math.floor(frame.height - padding * 2);
1858}
1859
1860export function mem(
1861  list: Array<unknown>,
1862  memFilter: Array<unknown>,
1863  startNS: number,
1864  endNS: number,
1865  totalNS: number,
1866  frame: Rect,
1867  use: boolean
1868): void {
1869  if (use && memFilter.length > 0) {
1870    for (let i = 0, len = memFilter.length; i < len; i++) {
1871      if (
1872        //@ts-ignore
1873        (memFilter[i].startTime || 0) + (memFilter[i].duration || 0) > startNS &&
1874        //@ts-ignore
1875        (memFilter[i].startTime || 0) < endNS
1876      ) {
1877        //@ts-ignore
1878        setMemFrame(memFilter[i], 5, startNS, endNS, totalNS, frame);
1879      } else {
1880        //@ts-ignore
1881        memFilter[i].frame = undefined;
1882      }
1883    }
1884    return;
1885  }
1886  memFilter.length = 0;
1887  //@ts-ignore
1888  setMemFilter(list, memFilter, startNS, endNS, totalNS, frame);
1889}
1890
1891function setMemFilter(
1892  list: Array<ProcessMemStruct>,
1893  memFilter: Array<ProcessMemStruct>,
1894  startNS: number,
1895  endNS: number,
1896  totalNS: number,
1897  frame: Rect
1898): void {
1899  if (list) {
1900    for (let i = 0, len = list.length; i < len; i++) {
1901      let it = list[i];
1902      if ((it.startTime || 0) + (it.duration || 0) > startNS && (it.startTime || 0) < endNS) {
1903        setMemFrame(list[i], 5, startNS, endNS, totalNS, frame);
1904        if (
1905          i > 0 &&
1906          (list[i - 1].frame?.x || 0) === (list[i].frame?.x || 0) &&
1907          (list[i - 1].frame?.width || 0) === (list[i].frame?.width || 0)
1908        ) {
1909        } else {
1910          memFilter.push(list[i]);
1911        }
1912      }
1913    }
1914  }
1915}
1916
1917export function drawWakeUpList(
1918  wakeUpListContext: CanvasRenderingContext2D | undefined | null,
1919  wake: WakeupBean | undefined | null,
1920  startNS: number,
1921  endNS: number,
1922  totalNS: number,
1923  frame: Rect,
1924  wakeup: WakeupBean | undefined = undefined,
1925  currentCpu: number | undefined | null = undefined,
1926  noVerticalLine = false
1927): void {
1928  if (!wakeUpListContext) {
1929    return;
1930  }
1931  if (wake) {
1932    let x1 = Math.floor(ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame));
1933    wakeUpListContext.beginPath();
1934    wakeUpListContext.lineWidth = 2;
1935    wakeUpListContext.fillStyle = '#000000';
1936    if (x1 > 0 && x1 < frame.x + frame.width) {
1937      if (!noVerticalLine) {
1938        wakeUpListContext.moveTo(x1, frame.y);
1939        wakeUpListContext.lineTo(x1, frame.y + frame.height);
1940      }
1941      if (currentCpu === wake.cpu) {
1942        let centerY = Math.floor(frame.y + frame.height / 2);
1943        wakeUpListContext.moveTo(x1, centerY - 6);
1944        wakeUpListContext.lineTo(x1 + 4, centerY);
1945        wakeUpListContext.lineTo(x1, centerY + 6);
1946        wakeUpListContext.lineTo(x1 - 4, centerY);
1947        wakeUpListContext.lineTo(x1, centerY - 6);
1948        wakeUpListContext.fill();
1949      }
1950    }
1951    if (wakeup) {
1952      drawWakeUpListIfWakeUp(wakeUpListContext, wake, startNS, endNS, totalNS, frame, wakeup, x1);
1953    }
1954    wakeUpListContext.strokeStyle = '#000000';
1955    wakeUpListContext.stroke();
1956    wakeUpListContext.closePath();
1957  }
1958}
1959
1960function drawWakeUpListIfWakeUp(
1961  wakeUpListContext: CanvasRenderingContext2D,
1962  wake: WakeupBean,
1963  startNS: number,
1964  endNS: number,
1965  totalNS: number,
1966  frame: Rect,
1967  wakeup: WakeupBean,
1968  x1: number
1969): void {
1970  let x2 = Math.floor(ns2x(wakeup.ts || 0, startNS, endNS, totalNS, frame));
1971  let y = frame.y + frame.height - 10;
1972  wakeUpListContext.moveTo(x1, y);
1973  wakeUpListContext.lineTo(x2, y);
1974  wakeUpListContext.moveTo(x2, y - 25);
1975  wakeUpListContext.lineTo(x2, y + 5);
1976  let s = ns2s((wakeup.ts || 0) - (wake.wakeupTime || 0));
1977  let wakeUpListDistance = x2 - x1;
1978  if (wakeUpListDistance > 12) {
1979    wakeUpListContext.moveTo(x1, y);
1980    wakeUpListContext.lineTo(x1 + 6, y - 3);
1981    wakeUpListContext.moveTo(x1, y);
1982    wakeUpListContext.lineTo(x1 + 6, y + 3);
1983    wakeUpListContext.moveTo(x2, y);
1984    wakeUpListContext.lineTo(x2 - 6, y - 3);
1985    wakeUpListContext.moveTo(x2, y);
1986    wakeUpListContext.lineTo(x2 - 6, y + 3);
1987    let measure = wakeUpListContext.measureText(s);
1988    let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
1989    let xStart = x1 + Math.floor(wakeUpListDistance / 2 - measure.width / 2);
1990    if (wakeUpListDistance > measure.width + 4) {
1991      wakeUpListContext.fillStyle = '#ffffff';
1992      wakeUpListContext.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4);
1993      wakeUpListContext.font = '10px solid';
1994      wakeUpListContext.fillStyle = '#000000';
1995      wakeUpListContext.textBaseline = 'bottom';
1996      wakeUpListContext.fillText(s, xStart, y - 2);
1997    }
1998  }
1999}
2000interface SearchNode {
2001  symbolName?: string;
2002  children: SearchNode[];
2003  searchShow?: boolean;
2004  isSearch?: boolean;
2005  parent?: SearchNode;
2006}
2007
2008export function findSearchNode(data: unknown[], search: string, parentSearch: boolean): void {
2009  search = search.toLocaleLowerCase();
2010  data.forEach((nodeIt) => {
2011    const node = nodeIt as SearchNode;
2012    if ((node.symbolName && node.symbolName.toLocaleLowerCase().includes(search) && search !== '') || parentSearch) {
2013      node.searchShow = true;
2014      node.isSearch = node.symbolName !== undefined && node.symbolName.toLocaleLowerCase().includes(search);
2015      let parentNode = node.parent;
2016      while (parentNode && !parentNode.searchShow) {
2017        parentNode.searchShow = true;
2018        parentNode = parentNode.parent;
2019      }
2020      if (node.isSearch && search !== '') {
2021        HiPerfStruct.bottomFindCount += 1;
2022      }
2023    } else {
2024      node.searchShow = false;
2025      node.isSearch = false;
2026    }
2027    if (node.children.length > 0) {
2028      findSearchNode(node.children, search, node.searchShow);
2029    }
2030  });
2031}
2032
2033// draw prio curve
2034// @ts-ignore
2035export function prioClickHandlerFun(param: unknown, row: TraceRow<unknown>, threadFilter: Array<ThreadStruct>, arr: unknown, oldVal: number): void {
2036  //@ts-ignore
2037  let maxCount = Math.max(...param.map((obj: unknown) => obj.count));
2038  //@ts-ignore
2039  let maxCountPrio = param.find((obj: unknown) => obj.count === maxCount).prio;//找出出现次数最多的优先级,为中位值
2040  //@ts-ignore
2041  let maxPrioDiff = Math.max(...param.map((obj: unknown) => Math.abs(obj.prio - Number(maxCountPrio))));//找出与中位值的最大diff
2042  let maxPointInterval = Math.ceil(maxPrioDiff / 4);//diff分成4份,每一份占多少px
2043
2044  for (let i = 0; i < threadFilter.length; i++) {
2045    const item = threadFilter[i];
2046    const preItem = threadFilter[i - 1];
2047    //给原始数据添加prio值
2048    let slice = Utils.getInstance().getSchedSliceMap().get(`${item.id}-${item.startTime}`);
2049    if (slice) {
2050      item.prio = slice!.priority;
2051    }
2052    //合并prio值相同的项提高画图速度
2053    if (
2054      item.prio &&
2055      (oldVal !== item.prio || i === threadFilter.length - 2 || i === threadFilter.length - 1)
2056    ) {
2057      configCurveY(row, item, maxCountPrio, maxPointInterval);
2058      //处理prio值变化前的
2059      if (i !== 0) {
2060        configCurveY(row, preItem, maxCountPrio, maxPointInterval);
2061        //@ts-ignore
2062        arr.push(preItem);
2063      }
2064      //@ts-ignore
2065      arr.push(item);
2066      oldVal = item.prio;
2067    }
2068  }
2069}
2070
2071//确定曲线波动时的y轴
2072//@ts-ignore
2073function configCurveY(row: TraceRow<unknown>, item: ThreadStruct, maxCountPrio: number, maxPointInterval: number): void {
2074  if (item.prio === Number(maxCountPrio)) {
2075    item.curveFloatY = 3 + 12 / 2 + row.translateY;
2076  } else if (item.prio! > Number(maxCountPrio)) {
2077    let prioHeight = Math.floor((item.prio! - Number(maxCountPrio)) / maxPointInterval) * 2;
2078    item.curveFloatY = 3 + 12 / 2 - prioHeight + row.translateY;
2079  } else if (item.prio! < Number(maxCountPrio)) {
2080    let prioHeight = Math.floor((Number(maxCountPrio) - item.prio!) / maxPointInterval) * 2;
2081    item.curveFloatY = 3 + 12 / 2 + prioHeight + row.translateY;
2082  }
2083}
2084
2085export function drawThreadCurve(context: CanvasRenderingContext2D, threadFilter: ThreadStruct, nextFilter: ThreadStruct): void {
2086  // 绘制曲线
2087  if (threadFilter.frame && nextFilter.frame) {
2088    let p1 = threadFilter;
2089    let p2 = nextFilter;
2090    let diff = p2.curveFloatY! >= p1.curveFloatY! ? p2.curveFloatY! - p1.curveFloatY! : p1.curveFloatY! - p2.curveFloatY!;
2091    let cp1x = p1.frame!.x + (p2.frame!.x - p1.frame!.x) / 5;
2092    let cp1y = p2.curveFloatY! >= p1.curveFloatY! ? p1.curveFloatY! - diff / 5 : p1.curveFloatY! + diff / 5;
2093    let cp2x = p2.frame!.x - (p2.frame!.x - p1.frame!.x) / 5;
2094    let cp2y = p2.curveFloatY! >= p1.curveFloatY! ? p2.curveFloatY! + diff / 5 : p2.curveFloatY! - diff / 5;
2095    context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.frame!.x, p2.curveFloatY!);
2096    context.lineWidth = 1;
2097    context.strokeStyle = '#ffc90e';
2098    context.lineCap = 'round';
2099  }
2100  context.stroke();
2101}
2102