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