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 { 17 BaseStruct, 18 Rect, 19 Render, 20 drawString, 21 isFrameContainPoint, 22 ns2x, 23 drawLoadingFrame, 24} from './ProcedureWorkerCommon'; 25import { TraceRow } from '../../component/trace/base/TraceRow'; 26import { ColorUtils } from '../../component/trace/base/ColorUtils'; 27import { type JsCpuProfilerChartFrame } from '../../bean/JsStruct'; 28import { SpSystemTrace } from '../../component/SpSystemTrace'; 29 30export class JsCpuProfilerRender extends Render { 31 renderMainThread( 32 req: { 33 useCache: boolean; 34 context: CanvasRenderingContext2D; 35 type: string; 36 }, 37 jsCpuProfilerRow: TraceRow<JsCpuProfilerStruct> 38 ): void { 39 let filter = jsCpuProfilerRow.dataListCache; 40 jsCpuProfiler( 41 filter, 42 TraceRow.range!.startNS, 43 TraceRow.range!.endNS, 44 TraceRow.range!.totalNS, // @ts-ignore 45 jsCpuProfilerRow.frame, 46 req.useCache || !TraceRow.range!.refresh 47 ); 48 drawLoadingFrame(req.context, filter, jsCpuProfilerRow); 49 req.context.beginPath(); 50 let jsCpuProfilerFind = false; 51 for (let re of filter) { 52 JsCpuProfilerStruct.draw(req.context, re); 53 setHoveStruct(jsCpuProfilerRow, re, jsCpuProfilerFind); 54 } 55 if (!jsCpuProfilerFind && jsCpuProfilerRow.isHover) { 56 JsCpuProfilerStruct.hoverJsCpuProfilerStruct = undefined; 57 } 58 req.context.closePath(); 59 } 60} 61function setHoveStruct( 62 jsCpuProfilerRow: TraceRow<JsCpuProfilerStruct>, 63 re: JsCpuProfilerStruct, 64 jsCpuProfilerFind: boolean 65): void { 66 if (jsCpuProfilerRow.isHover) { 67 if ( 68 re.endTime - re.startTime === 0 || 69 re.endTime - re.startTime === null || 70 re.endTime - re.startTime === undefined 71 ) { 72 if ( 73 re.frame && 74 jsCpuProfilerRow.hoverX >= re.frame.x - 5 && 75 jsCpuProfilerRow.hoverX <= re.frame.x + 5 && 76 jsCpuProfilerRow.hoverY >= re.frame.y && 77 jsCpuProfilerRow.hoverY <= re.frame.y + re.frame.height 78 ) { 79 JsCpuProfilerStruct.hoverJsCpuProfilerStruct = re; 80 jsCpuProfilerFind = true; 81 } 82 } else { 83 if (re.frame && isFrameContainPoint(re.frame, jsCpuProfilerRow.hoverX, jsCpuProfilerRow.hoverY)) { 84 JsCpuProfilerStruct.hoverJsCpuProfilerStruct = re; 85 jsCpuProfilerFind = true; 86 } 87 } 88 } 89} 90export function jsCpuProfiler( 91 filter: Array<JsCpuProfilerStruct>, 92 startNS: number, 93 endNS: number, 94 totalNS: number, 95 frame: Rect, 96 use: boolean 97): void { 98 if (use && filter.length > 0) { 99 for (let i = 0, len = filter.length; i < len; i++) { 100 if ((filter[i].startTime || 0) + (filter[i].totalTime || 0) >= startNS && (filter[i].startTime || 0) <= endNS) { 101 JsCpuProfilerStruct.setJsCpuProfilerFrame(filter[i], startNS, endNS, totalNS, frame); 102 } else { 103 filter[i].frame = undefined; 104 } 105 } 106 } 107} 108 109const padding = 1; 110export function JsCpuProfilerStructOnClick( 111 clickRowType: string, 112 sp: SpSystemTrace, 113 row: TraceRow<JsCpuProfilerStruct>, 114 entry?: JsCpuProfilerStruct 115): Promise<unknown> { 116 return new Promise((resolve, reject) => { 117 if (clickRowType === TraceRow.ROW_TYPE_JS_CPU_PROFILER) { 118 if (row.findHoverStruct) { 119 row.findHoverStruct(); 120 } else { 121 JsCpuProfilerStruct.hoverJsCpuProfilerStruct = 122 JsCpuProfilerStruct.hoverJsCpuProfilerStruct || row.getHoverStruct(); 123 } 124 } 125 if (clickRowType === TraceRow.ROW_TYPE_JS_CPU_PROFILER && (JsCpuProfilerStruct.hoverJsCpuProfilerStruct || entry)) { 126 JsCpuProfilerStruct.selectJsCpuProfilerStruct = entry || JsCpuProfilerStruct.hoverJsCpuProfilerStruct; 127 let selectStruct = JsCpuProfilerStruct.selectJsCpuProfilerStruct; 128 let dataArr: Array<JsCpuProfilerChartFrame> = []; 129 let parentIdArr: Array<number> = []; 130 let that = sp; 131 getTopJsCpuProfilerStruct(selectStruct!.parentId, selectStruct!, that, dataArr, parentIdArr); 132 that.traceSheetEL?.displayJsProfilerData(dataArr); 133 reject(new Error()); 134 } else { 135 resolve(null); 136 } 137 }); 138} 139 140function getTopJsCpuProfilerStruct( 141 parentId: number, 142 selectStruct: JsCpuProfilerStruct, 143 that: SpSystemTrace, 144 dataArr: Array<JsCpuProfilerChartFrame> = [], 145 parentIdArr: Array<number> = [] 146): void { 147 if (parentId === -1 && selectStruct.parentId === -1) { 148 // 点击的函数是第一层,直接设置其children的isSelect为true,不用重新算totalTime 149 let data = that.chartManager!.arkTsChart.chartFrameMap.get(selectStruct!.id); 150 if (data && dataArr.length === 0) { 151 let copyData = JSON.parse(JSON.stringify(data)); 152 setSelectChildrenState(copyData); 153 dataArr.push(copyData); 154 } 155 } else { 156 let parent = that.chartManager!.arkTsChart.chartFrameMap.get(parentId); 157 if (parent) { 158 parentIdArr.push(parent.id); 159 getTopJsCpuProfilerStruct(parent.parentId!, selectStruct, that, dataArr, parentIdArr); 160 if (parent.parentId === -1 && dataArr.length === 0) { 161 let data = that.chartManager!.arkTsChart.chartFrameMap.get(parent.id); 162 let copyParent = JSON.parse(JSON.stringify(data)); 163 copyParent.totalTime = selectStruct.totalTime; 164 copyParent.selfTime = 0; 165 // depth为0的isSelect改为true 166 copyParent.isSelect = true; 167 if (copyParent.children.length > 0) { 168 getSelectStruct(copyParent, selectStruct, parentIdArr); 169 } 170 dataArr.push(copyParent); 171 } 172 } 173 } 174} 175 176function getSelectStruct( 177 data: JsCpuProfilerChartFrame, 178 selectStruct: JsCpuProfilerStruct, 179 parentIdArr: number[] 180): void { 181 for (let child of data.children) { 182 if (child === null) { 183 continue; 184 } 185 if (child.id === selectStruct!.id) { 186 // 将点击的函数的children的isSelect改为true 187 setSelectChildrenState(child); 188 } else { 189 getSelectStruct(child, selectStruct, parentIdArr); 190 } 191 if (parentIdArr.includes(child.id)) { 192 child.isSelect = true; 193 child.totalTime = selectStruct.totalTime; 194 child.selfTime = 0; 195 } 196 } 197} 198 199function setSelectChildrenState(data: JsCpuProfilerChartFrame): void { 200 data.isSelect = true; 201 if (data.children.length > 0) { 202 for (let child of data.children) { 203 if (child === null) { 204 continue; 205 } 206 setSelectChildrenState(child); 207 } 208 } 209} 210 211export class JsCpuProfilerStruct extends BaseStruct { 212 static lastSelectJsCpuProfilerStruct: JsCpuProfilerStruct | undefined; 213 static selectJsCpuProfilerStruct: JsCpuProfilerStruct | undefined; 214 static hoverJsCpuProfilerStruct: JsCpuProfilerStruct | undefined; 215 id: number = 0; 216 name: string = ''; 217 startTime: number = 0; 218 endTime: number = 0; 219 selfTime: number = 0; 220 totalTime: number = 0; 221 url: string = ''; 222 depth: number = 0; 223 parentId: number = 0; 224 children!: Array<JsCpuProfilerChartFrame>; 225 isSelect: boolean = false; 226 227 static setJsCpuProfilerFrame( 228 jsCpuProfilerNode: JsCpuProfilerStruct, 229 startNS: number, 230 endNS: number, 231 totalNS: number, 232 frame: Rect 233 ): void { 234 let x1: number; 235 let x2: number; 236 if ((jsCpuProfilerNode.startTime || 0) > startNS && (jsCpuProfilerNode.startTime || 0) < endNS) { 237 x1 = ns2x(jsCpuProfilerNode.startTime || 0, startNS, endNS, totalNS, frame); 238 } else { 239 x1 = 0; 240 } 241 if ( 242 (jsCpuProfilerNode.startTime || 0) + (jsCpuProfilerNode.totalTime || 0) > startNS && 243 (jsCpuProfilerNode.startTime || 0) + (jsCpuProfilerNode.totalTime || 0) < endNS 244 ) { 245 x2 = ns2x( 246 (jsCpuProfilerNode.startTime || 0) + (jsCpuProfilerNode.totalTime || 0), 247 startNS, 248 endNS, 249 totalNS, 250 frame 251 ); 252 } else { 253 x2 = frame.width; 254 } 255 if (!jsCpuProfilerNode.frame) { 256 jsCpuProfilerNode.frame = new Rect(0, 0, 0, 0); 257 } 258 let getV: number = x2 - x1 < 1 ? 1 : x2 - x1; 259 jsCpuProfilerNode.frame.x = Math.floor(x1); 260 jsCpuProfilerNode.frame.y = jsCpuProfilerNode.depth * 20; 261 jsCpuProfilerNode.frame.width = Math.ceil(getV); 262 jsCpuProfilerNode.frame.height = 20; 263 } 264 265 static draw(jsCpuProfilerCtx: CanvasRenderingContext2D, data: JsCpuProfilerStruct): void { 266 if (data.frame) { 267 if (data.endTime - data.startTime === undefined || data.endTime - data.startTime === null) { 268 } else { 269 jsCpuProfilerCtx.globalAlpha = 1; 270 if (data.name === '(program)') { 271 jsCpuProfilerCtx.fillStyle = '#ccc'; 272 } else if (data.name === '(idle)') { 273 jsCpuProfilerCtx.fillStyle = '#f0f0f0'; 274 } else { 275 jsCpuProfilerCtx.fillStyle = 276 ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', 0, ColorUtils.FUNC_COLOR.length)]; 277 } 278 let miniHeight = 20; 279 if (JsCpuProfilerStruct.hoverJsCpuProfilerStruct && data === JsCpuProfilerStruct.hoverJsCpuProfilerStruct) { 280 jsCpuProfilerCtx.globalAlpha = 0.7; 281 } 282 jsCpuProfilerCtx.fillRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2); 283 if (data.frame.width > 8) { 284 jsCpuProfilerCtx.lineWidth = 1; 285 jsCpuProfilerCtx.fillStyle = ColorUtils.funcTextColor( 286 ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', 0, ColorUtils.FUNC_COLOR.length)] 287 ); 288 jsCpuProfilerCtx.textBaseline = 'middle'; 289 drawString(jsCpuProfilerCtx, `${data.name || ''}`, 4, data.frame, data); 290 } 291 if ( 292 JsCpuProfilerStruct.selectJsCpuProfilerStruct && 293 JsCpuProfilerStruct.equals(JsCpuProfilerStruct.selectJsCpuProfilerStruct, data) 294 ) { 295 jsCpuProfilerCtx.strokeStyle = '#000'; 296 jsCpuProfilerCtx.lineWidth = 2; 297 jsCpuProfilerCtx.strokeRect( 298 data.frame.x + 1, 299 data.frame.y + 1, 300 data.frame.width - 2, 301 miniHeight - padding * 2 - 2 302 ); 303 } 304 } 305 } 306 } 307 static equals(d1: JsCpuProfilerStruct, d2: JsCpuProfilerStruct): boolean { 308 return ( 309 d1 && 310 d2 && 311 d1.id === d2.id && 312 d1.name === d2.name && 313 d1.url === d2.url && 314 d1.depth === d2.depth && 315 d1.totalTime === d2.totalTime && 316 d1.selfTime === d2.selfTime && 317 d1.startTime === d2.startTime && 318 d1.endTime === d2.endTime 319 ); 320 } 321} 322