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 { SpApplication } from '../SpApplication';
17import { Rect } from '../component/trace/timer-shaft/Rect';
18import { warn } from '../../log/Log';
19import { BaseStruct, drawString } from '../database/ui-worker/ProcedureWorkerCommon';
20
21const padding: number = 1;
22const rectHeight = 20;
23const lightBlue = {
24  r: 82,
25  g: 145,
26  b: 255,
27  a: 0.9,
28};
29const lightGreen = {
30  r: 132,
31  g: 200,
32  b: 112,
33  a: 0.9,
34};
35
36export class ChartStruct extends BaseStruct {
37  static hoverFuncStruct: ChartStruct | undefined;
38  static selectFuncStruct: ChartStruct | undefined;
39  static lastSelectFuncStruct: ChartStruct | undefined;
40  isDraw = false; // 是否绘制,太小的不绘制
41  depth: number = 0;
42  symbol: string = '';
43  lib: string = '';
44
45  id?: string;
46  eventType?: string;
47  parentId?: string;
48  self?: string; // only perf
49  eventPercent?: string; // only perf
50  title?: string;
51
52  size: number = 0; // 实际size
53  count: number = 0; // 实际count
54  eventCount: number = 0;
55  dur: number = 0; // 实际dur
56  //搜索后会根据搜索匹配的函数的值赋值给parent
57  searchSize: number = 0; //
58  searchCount: number = 0;
59  searchDur: number = 0;
60  searchEventCount: number = 0;
61  //点击绘制的size在搜索的基础上,赋值给parent
62  drawSize: number = 0;
63  drawCount: number = 0;
64  drawDur: number = 0;
65  drawEventCount: number = 0;
66
67  parent: ChartStruct | undefined;
68  children: Array<ChartStruct> = [];
69  percent: number = 0; // 0 - 1 该node所占整体的百分比
70  addr: string = '';
71  isSearch: boolean = false;
72  isChartSelect: boolean = false; // 是否为点选的调用链
73  isChartSelectParent: boolean = false; // 用来显示灰色
74  tsArray: Array<number> = [];
75  countArray: Array<number> = [];
76  durArray: Array<number> = [];
77  isThread: boolean = false;
78  isProcess: boolean = false;
79  isJsStack: boolean = false;
80}
81
82export enum ChartMode {
83  Byte, // Native Memory
84  Count, // Perf
85  Duration, // eBpf
86  EventCount, //cycles
87}
88
89export function setFuncFrame(node: ChartStruct, canvasFrame: Rect, total: number, mode: ChartMode): void {
90  if (!node.frame) {
91    node.frame = new Rect(0, 0, 0, 0);
92  }
93  // filter depth is 0
94  if (node.parent) {
95    let idx = node.parent.children.indexOf(node);
96    if (idx === 0) {
97      node.frame!.x = node.parent.frame!.x;
98    } else {
99      // set x by left frame. left frame is parent.children[idx - 1]
100      node.frame.x = node.parent.children[idx - 1].frame!.x + node.parent.children[idx - 1].frame!.width;
101    }
102    if (node.parent?.isChartSelect && !node.isChartSelect) {
103      node.frame!.width = 0;
104    } else {
105      switch (mode) {
106        case ChartMode.Byte:
107          node.frame!.width = Math.floor(((node.drawSize || node.size) / total) * canvasFrame.width);
108          break;
109        case ChartMode.Count:
110          node.frame!.width = Math.floor(((node.drawCount || node.count) / total) * canvasFrame.width);
111          break;
112        case ChartMode.Duration:
113          node.frame!.width = Math.floor(((node.drawDur || node.dur) / total) * canvasFrame.width);
114          break;
115        case ChartMode.EventCount:
116          node.frame!.width = Math.floor(((node.drawEventCount || node.eventCount) / total) * canvasFrame.width);
117          break;
118        default:
119          warn('not match ChartMode');
120      }
121    }
122
123    node.frame!.y = node.parent.frame!.y + rectHeight;
124    node.frame!.height = rectHeight;
125  }
126}
127
128/**
129 * draw rect
130 * @param canvasCtx CanvasRenderingContext2D
131 * @param node rect which is need draw
132 * @param percent function size or count / total size or count
133 */
134export function draw(canvasCtx: CanvasRenderingContext2D, node: ChartStruct): void {
135  let spApplication = <SpApplication>document.getElementsByTagName('sp-application')[0];
136  if (!node.frame) {
137    return;
138  }
139  //主体
140  const drawHeight = rectHeight - padding * 2; //绘制方块上下留一个像素
141  if (node.depth === 0 || (node.isChartSelectParent && node !== ChartStruct.selectFuncStruct)) {
142    canvasCtx.fillStyle = `rgba(${lightBlue.g}, ${lightBlue.g}, ${lightBlue.g}, ${lightBlue.a})`;
143  } else {
144    if (node.isSearch) {
145      canvasCtx.fillStyle = `rgba(${lightBlue.r}, ${lightBlue.g}, ${lightBlue.b}, ${lightBlue.a})`;
146    } else {
147      if (node.isJsStack) {
148        canvasCtx.fillStyle = `rgba(${lightGreen.r}, ${lightGreen.g}, ${lightGreen.b}, ${lightGreen.a})`;
149      } else {
150        canvasCtx.fillStyle = getHeatColor(node.percent);
151      }
152    }
153  }
154  canvasCtx.fillRect(node.frame.x, node.frame.y, node.frame.width, drawHeight);
155  //边框
156  canvasCtx.lineWidth = 0.4;
157  if (isHover(node)) {
158    if (spApplication.dark) {
159      canvasCtx.strokeStyle = '#fff';
160    } else {
161      canvasCtx.strokeStyle = '#000';
162    }
163  } else {
164    if (spApplication.dark) {
165      canvasCtx.strokeStyle = '#000';
166    } else {
167      canvasCtx.strokeStyle = '#fff';
168    }
169  }
170  canvasCtx.strokeRect(node.frame.x, node.frame.y, node.frame.width - canvasCtx.lineWidth, drawHeight);
171  //文字
172  if (node.frame.width > 10) {
173    if (node.percent > 0.6 || node.isSearch) {
174      canvasCtx.fillStyle = '#fff';
175    } else {
176      canvasCtx.fillStyle = '#000';
177    }
178    drawString(canvasCtx, splitSymbol(node), 5, node.frame, node);
179  }
180  node.isDraw = true;
181}
182
183function splitSymbol(node: ChartStruct): string {
184  if (node.depth === 0 || node.isProcess || node.isThread) {
185    return node.symbol;
186  }
187  return node.symbol.split(' (')[0];
188}
189
190/**
191 * 火焰图颜色计算,根据每个node占总大小的百分比调整
192 * @param widthPercentage 百分比
193 * @returns rbg
194 */
195function getHeatColor(widthPercentage: number): string {
196  return `rgba(
197    ${Math.floor(245 + 10 * (1 - widthPercentage))},
198    ${Math.floor(110 + 105 * (1 - widthPercentage))},
199    ${100},
200    0.9)`;
201}
202
203function isHover(data: ChartStruct): boolean {
204  return ChartStruct.hoverFuncStruct === data;
205}
206