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 sourceFile: string = ''; 81 lineNumber: Set<number> = new Set<number>(); 82} 83 84export enum ChartMode { 85 Byte, // Native Memory 86 Count, // Perf 87 Duration, // eBpf 88 EventCount, //cycles 89} 90 91export function setFuncFrame(node: ChartStruct, canvasFrame: Rect, total: number, mode: ChartMode): void { 92 if (!node.frame) { 93 node.frame = new Rect(0, 0, 0, 0); 94 } 95 // filter depth is 0 96 if (node.parent) { 97 let idx = node.parent.children.indexOf(node); 98 if (idx === 0) { 99 node.frame!.x = node.parent.frame!.x; 100 } else { 101 // set x by left frame. left frame is parent.children[idx - 1] 102 node.frame.x = node.parent.children[idx - 1].frame!.x + node.parent.children[idx - 1].frame!.width; 103 } 104 if (node.parent?.isChartSelect && !node.isChartSelect) { 105 node.frame!.width = 0; 106 } else { 107 switch (mode) { 108 case ChartMode.Byte: 109 node.frame!.width = Math.floor(((node.drawSize || node.size) / total) * canvasFrame.width); 110 break; 111 case ChartMode.Count: 112 node.frame!.width = Math.floor(((node.drawCount || node.count) / total) * canvasFrame.width); 113 break; 114 case ChartMode.Duration: 115 node.frame!.width = Math.floor(((node.drawDur || node.dur) / total) * canvasFrame.width); 116 break; 117 case ChartMode.EventCount: 118 node.frame!.width = Math.floor(((node.drawEventCount || node.eventCount) / total) * canvasFrame.width); 119 break; 120 default: 121 warn('not match ChartMode'); 122 } 123 } 124 125 node.frame!.y = node.parent.frame!.y + rectHeight; 126 node.frame!.height = rectHeight; 127 } 128} 129 130/** 131 * draw rect 132 * @param canvasCtx CanvasRenderingContext2D 133 * @param node rect which is need draw 134 * @param percent function size or count / total size or count 135 */ 136export function draw(canvasCtx: CanvasRenderingContext2D, node: ChartStruct): void { 137 let spApplication = <SpApplication>document.getElementsByTagName('sp-application')[0]; 138 if (!node.frame) { 139 return; 140 } 141 //主体 142 const drawHeight = rectHeight - padding * 2; //绘制方块上下留一个像素 143 if (node.depth === 0 || (node.isChartSelectParent && node !== ChartStruct.selectFuncStruct)) { 144 canvasCtx.fillStyle = `rgba(${lightBlue.g}, ${lightBlue.g}, ${lightBlue.g}, ${lightBlue.a})`; 145 } else { 146 if (node.isSearch) { 147 canvasCtx.fillStyle = `rgba(${lightBlue.r}, ${lightBlue.g}, ${lightBlue.b}, ${lightBlue.a})`; 148 } else { 149 if (node.isJsStack) { 150 canvasCtx.fillStyle = `rgba(${lightGreen.r}, ${lightGreen.g}, ${lightGreen.b}, ${lightGreen.a})`; 151 } else { 152 canvasCtx.fillStyle = getHeatColor(node.percent); 153 } 154 } 155 } 156 canvasCtx.fillRect(node.frame.x, node.frame.y, node.frame.width, drawHeight); 157 //边框 158 canvasCtx.lineWidth = 0.4; 159 if (isHover(node)) { 160 if (spApplication.dark) { 161 canvasCtx.strokeStyle = '#fff'; 162 } else { 163 canvasCtx.strokeStyle = '#000'; 164 } 165 } else { 166 if (spApplication.dark) { 167 canvasCtx.strokeStyle = '#000'; 168 } else { 169 canvasCtx.strokeStyle = '#fff'; 170 } 171 } 172 canvasCtx.strokeRect(node.frame.x, node.frame.y, node.frame.width - canvasCtx.lineWidth, drawHeight); 173 //文字 174 if (node.frame.width > 10) { 175 if (node.percent > 0.6 || node.isSearch) { 176 canvasCtx.fillStyle = '#fff'; 177 } else { 178 canvasCtx.fillStyle = '#000'; 179 } 180 drawString(canvasCtx, splitSymbol(node), 5, node.frame, node); 181 } 182 node.isDraw = true; 183} 184 185function splitSymbol(node: ChartStruct): string { 186 if (node.depth === 0 || node.isProcess || node.isThread) { 187 return node.symbol; 188 } 189 return node.symbol.split(' (')[0]; 190} 191 192/** 193 * 火焰图颜色计算,根据每个node占总大小的百分比调整 194 * @param widthPercentage 百分比 195 * @returns rbg 196 */ 197function getHeatColor(widthPercentage: number): string { 198 return `rgba( 199 ${Math.floor(245 + 10 * (1 - widthPercentage))}, 200 ${Math.floor(110 + 105 * (1 - widthPercentage))}, 201 ${100}, 202 0.9)`; 203} 204 205function isHover(data: ChartStruct): boolean { 206 return ChartStruct.hoverFuncStruct === data; 207} 208