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