* Copyright (C) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SpApplication } from '../SpApplication';
import { Rect } from '../component/trace/timer-shaft/Rect';
import { warn } from '../../log/Log';
import { BaseStruct, drawString } from '../database/ui-worker/ProcedureWorkerCommon';
const padding: number = 1;
const rectHeight = 20;
const lightBlue = {
r: 82,
g: 145,
b: 255,
a: 0.9,
};
const lightGreen = {
r: 132,
g: 200,
b: 112,
a: 0.9,
};
const purple = {
r: 147,
g: 112,
b: 219,
a: 0.9,
};
export class ChartStruct extends BaseStruct {
static hoverFuncStruct: ChartStruct | undefined;
static selectFuncStruct: ChartStruct | undefined;
static lastSelectFuncStruct: ChartStruct | undefined;
static tempSelectStruct: ChartStruct | undefined;
isDraw = false;
depth: number = 0;
symbol: string = '';
lib: string = '';
id?: string;
eventType?: string;
parentId?: string;
self?: string;
eventPercent?: string;
title?: string;
size: number = 0;
count: number = 0;
eventCount: number = 0;
dur: number = 0;
searchSize: number = 0;
searchCount: number = 0;
searchDur: number = 0;
searchEventCount: number = 0;
drawSize: number = 0;
drawCount: number = 0;
drawDur: number = 0;
drawEventCount: number = 0;
parent: ChartStruct | undefined;
children: Array<ChartStruct> = [];
percent: number = 0;
addr: string = '';
isReverseFilter:boolean = false;
isSearch: boolean = false;
isChartSelect: boolean = false;
isChartSelectParent: boolean = false;
tsArray: Array<number> = [];
countArray: Array<number> = [];
durArray: Array<number> = [];
isThread: boolean = false;
isProcess: boolean = false;
isJsStack: boolean = false;
isAsyncStack: boolean = false;
sourceFile: string = '';
lineNumber: Set<number> = new Set<number>();
}
export enum ChartMode {
Byte,
Count,
Duration,
EventCount,
}
export function setFuncFrame(node: ChartStruct, canvasFrame: Rect, total: number, mode: ChartMode): void {
if (!node.frame) {
node.frame = new Rect(0, 0, 0, 0);
}
if (node.parent) {
let idx = node.parent.children.indexOf(node);
if (idx === 0) {
node.frame!.x = node.parent.frame!.x;
} else {
node.frame.x = node.parent.children[idx - 1].frame!.x + node.parent.children[idx - 1].frame!.width;
}
if (node.parent?.isChartSelect && !node.isChartSelect) {
node.frame!.width = 0;
} else {
switch (mode) {
case ChartMode.Byte:
node.frame!.width = Math.floor(((node.drawSize || node.size) / total) * canvasFrame.width);
break;
case ChartMode.Count:
node.frame!.width = Math.floor(((node.drawCount || node.count) / total) * canvasFrame.width);
break;
case ChartMode.Duration:
node.frame!.width = Math.floor(((node.drawDur || node.dur) / total) * canvasFrame.width);
break;
case ChartMode.EventCount:
node.frame!.width = Math.floor(((node.drawEventCount || node.eventCount) / total) * canvasFrame.width);
break;
default:
warn('not match ChartMode');
}
}
node.frame!.y = node.parent.frame!.y + rectHeight;
node.frame!.height = rectHeight;
}
}
* draw rect
* @param canvasCtx CanvasRenderingContext2D
* @param node rect which is need draw
* @param percent function size or count / total size or count
*/
export function draw(canvasCtx: CanvasRenderingContext2D, node: ChartStruct): void {
let spApplication = <SpApplication>document.getElementsByTagName('sp-application')[0];
if (!node.frame) {
return;
}
const drawHeight = rectHeight - padding * 2;
if (node.depth === 0 || (node.isChartSelectParent && node !== ChartStruct.selectFuncStruct)) {
canvasCtx.fillStyle = `rgba(${lightBlue.g}, ${lightBlue.g}, ${lightBlue.g}, ${lightBlue.a})`;
} else {
if (node.isSearch) {
canvasCtx.fillStyle = `rgba(${lightBlue.r}, ${lightBlue.g}, ${lightBlue.b}, ${lightBlue.a})`;
} else {
if (node.isJsStack) {
canvasCtx.fillStyle = `rgba(${lightGreen.r}, ${lightGreen.g}, ${lightGreen.b}, ${lightGreen.a})`;
} else {
if (node.isAsyncStack) {
canvasCtx.fillStyle = `rgba(${purple.r}, ${purple.g}, ${purple.b}, ${purple.a})`;
} else {
canvasCtx.fillStyle = getHeatColor(node.percent);
}
}
}
}
canvasCtx.fillRect(node.frame.x, node.frame.y, node.frame.width, drawHeight);
canvasCtx.lineWidth = 0.4;
if (isHover(node)) {
if (spApplication.dark) {
canvasCtx.strokeStyle = '#fff';
} else {
canvasCtx.strokeStyle = '#000';
}
} else {
if (spApplication.dark) {
canvasCtx.strokeStyle = '#000';
} else {
canvasCtx.strokeStyle = '#fff';
}
}
canvasCtx.strokeRect(node.frame.x, node.frame.y, node.frame.width - canvasCtx.lineWidth, drawHeight);
if (node.frame.width > 10) {
if (node.percent > 0.6 || node.isSearch) {
canvasCtx.fillStyle = '#fff';
} else {
canvasCtx.fillStyle = '#000';
}
drawString(canvasCtx, splitSymbol(node), 5, node.frame, node);
}
node.isDraw = true;
}
export function splitSymbol(node: ChartStruct): string {
if (node.depth === 0 || node.isProcess || node.isThread) {
return node.symbol;
}
return node.symbol.split(' (')[0];
}
* 火焰图颜色计算,根据每个node占总大小的百分比调整
* @param widthPercentage 百分比
* @returns rbg
*/
function getHeatColor(widthPercentage: number): string {
return `rgba(
${Math.floor(245 + 10 * (1 - widthPercentage))},
${Math.floor(110 + 105 * (1 - widthPercentage))},
${100},
0.9)`;
}
function isHover(data: ChartStruct): boolean {
return ChartStruct.hoverFuncStruct === data;
}