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 { ColorUtils } from '../../component/trace/base/ColorUtils'; 17import { TraceRow } from '../../component/trace/base/TraceRow'; 18import { 19 isFrameContainPoint, 20 ns2x, 21 Render, 22 RequestMessage, 23 drawFunString, 24 drawLoadingFrame, 25 Rect, 26} from './ProcedureWorkerCommon'; 27import { FuncStruct as BaseFuncStruct } from '../../bean/FuncStruct'; 28import { FlagsConfig } from '../../component/SpFlags'; 29import { TabPaneTaskFrames } from '../../component/trace/sheet/task/TabPaneTaskFrames'; 30import { SpSystemTrace } from '../../component/SpSystemTrace'; 31import { Utils } from '../../component/trace/base/Utils'; 32 33export class FuncRender { 34 renderMainThread( 35 req: { useCache: boolean; context: CanvasRenderingContext2D; type: string }, 36 row: TraceRow<FuncStruct> 37 ): void { 38 let funcList = row.dataList; 39 let funcFilter = row.dataListCache; 40 func( 41 funcList, 42 funcFilter, 43 TraceRow.range!.startNS, 44 TraceRow.range!.endNS, 45 TraceRow.range!.totalNS, 46 row.frame, 47 req.useCache || !TraceRow.range!.refresh, 48 row.funcExpand, 49 row.rowParentId 50 ); 51 drawLoadingFrame(req.context, funcFilter, row, true); 52 req.context.beginPath(); 53 let funcFind = false; 54 let flagConfig = FlagsConfig.getFlagsConfig('TaskPool'); 55 for (let re of funcFilter) { 56 FuncStruct.draw(req.context, re, flagConfig); 57 if (row.isHover) { 58 if (re.dur === 0 || re.dur === null || re.dur === undefined) { 59 if ( 60 re.frame && 61 re.itid && 62 row.hoverX >= re.frame.x - 5 && 63 row.hoverX <= re.frame.x + 5 && 64 row.hoverY >= re.frame.y && 65 row.hoverY <= re.frame.y + re.frame.height 66 ) { 67 FuncStruct.hoverFuncStruct = re; 68 funcFind = true; 69 } 70 } else { 71 if (re.frame && re.itid && isFrameContainPoint(re.frame, row.hoverX, row.hoverY)) { 72 FuncStruct.hoverFuncStruct = re; 73 funcFind = true; 74 } 75 } 76 } 77 } 78 if (!funcFind && row.isHover) { 79 FuncStruct.hoverFuncStruct = undefined; 80 } 81 req.context.closePath(); 82 } 83 84 render(req: RequestMessage, list: Array<FuncStruct>, filter: Array<FuncStruct>): void { } 85} 86 87export function func( 88 funcList: Array<FuncStruct>, 89 funcFilter: Array<FuncStruct>, 90 startNS: number, 91 endNS: number, 92 totalNS: number, 93 frame: Rect, 94 use: boolean, 95 expand: boolean, 96 rowParentId: string | null | undefined 97): void { 98 if (use && funcFilter.length > 0) { 99 if (rowParentId === 'UserPluginsRows' && !expand) { 100 funcFilter = funcFilter.filter((it) => it.depth === 0); 101 } 102 for (let i = 0, len = funcFilter.length; i < len; i++) { 103 if ((funcFilter[i].startTs || 0) + (funcFilter[i].dur || 0) >= startNS && (funcFilter[i].startTs || 0) <= endNS) { 104 FuncStruct.setFuncFrame(funcFilter[i], 0, startNS, endNS, totalNS, frame); 105 } else { 106 funcFilter[i].frame = undefined; 107 } 108 } 109 return; 110 } 111 funcFilter.length = 0; 112 if (funcList) { 113 let groups = funcList 114 .filter( 115 (it) => 116 (it.startTs ?? 0) + (it.dur ?? 0) >= startNS && 117 (it.startTs ?? 0) <= endNS && 118 ((!expand && it.depth === 0) || expand) 119 ) 120 .map((it) => { 121 FuncStruct.setFuncFrame(it, 0, startNS, endNS, totalNS, frame); 122 return it; 123 }) 124 .reduce((pre, current, index, arr) => { 125 //@ts-ignore 126 (pre[`${current.frame.x}-${current.depth}`] = pre[`${current.frame.x}-${current.depth}`] || []).push(current); 127 return pre; 128 }, {}); 129 Reflect.ownKeys(groups).map((kv) => { 130 //@ts-ignore 131 let arr = groups[kv].sort((a: FuncStruct, b: FuncStruct) => b.dur - a.dur); 132 funcFilter.push(arr[0]); 133 }); 134 } 135} 136export function funcStructOnClick( 137 clickRowType: string, 138 sp: SpSystemTrace, 139 row: TraceRow<FuncStruct> | undefined, 140 scrollToFuncHandler: Function, 141 entry?: FuncStruct 142): Promise<unknown> { 143 return new Promise((resolve, reject) => { 144 if (clickRowType === TraceRow.ROW_TYPE_FUNC && (FuncStruct.hoverFuncStruct || entry)) { 145 if (FuncStruct.funcSelect) { 146 sp.observerScrollHeightEnable = false; 147 TabPaneTaskFrames.TaskArray = []; 148 sp.removeLinkLinesByBusinessType('task'); 149 FuncStruct.firstSelectFuncStruct = FuncStruct.selectFuncStruct; 150 let hoverFuncStruct = entry || FuncStruct.hoverFuncStruct; 151 FuncStruct.selectFuncStruct = hoverFuncStruct; 152 sp.timerShaftEL?.drawTriangle(FuncStruct.selectFuncStruct!.startTs || 0, 'inverted'); 153 TraceRow.rangeSelectObject = undefined; 154 let flagConfig = FlagsConfig.getFlagsConfig('TaskPool'); 155 let showTabArray: Array<string> = ['current-selection']; 156 if (flagConfig!.TaskPool === 'Enabled') { 157 if (FuncStruct.selectFuncStruct?.funName) { 158 if (FuncStruct.selectFuncStruct.funName.indexOf('H:Task ') >= 0) { 159 showTabArray.push('box-task-frames'); 160 sp.drawTaskPollLine(row); 161 } 162 } 163 } 164 sp.traceSheetEL?.displayFuncData( 165 showTabArray, 166 // @ts-ignore 167 row?.namePrefix, 168 FuncStruct.selectFuncStruct!, 169 scrollToFuncHandler, 170 (datas: unknown, str: string, binderTid: number) => { 171 sp.removeLinkLinesByBusinessType('func'); 172 if (str === 'binder-to') { 173 //@ts-ignore 174 datas.forEach((data: { tid: unknown; pid: unknown }) => { 175 //@ts-ignore 176 let endParentRow = sp.shadowRoot?.querySelector<TraceRow<unknown>>( 177 `trace-row[row-id='${data.pid}'][folder]` 178 ); 179 sp.drawFuncLine(endParentRow, hoverFuncStruct, data, binderTid); 180 }); 181 } 182 }, 183 (dataList: FuncStruct[]): void => { 184 dataList.sort((leftData: FuncStruct, rightData: FuncStruct) => leftData.ts! - rightData.ts!); 185 FuncStruct.selectLineFuncStruct = dataList; 186 sp.resetDistributedLine(); 187 } 188 ); 189 sp.refreshCanvas(true); 190 sp.timerShaftEL?.modifyFlagList(undefined); 191 } 192 reject(new Error()); 193 } else { 194 resolve(null); 195 } 196 }); 197} 198export class FuncStruct extends BaseFuncStruct { 199 static textColor: string; 200 [x: string]: unknown; 201 static hoverFuncStruct: FuncStruct | undefined; 202 static selectFuncStruct: FuncStruct | undefined; 203 static selectLineFuncStruct: Array<FuncStruct> = []; 204 static firstSelectFuncStruct: FuncStruct | undefined; 205 flag: string | undefined; // 570000 206 textMetricsWidth: number | undefined; 207 static funcSelect: boolean = true; 208 pid: number | undefined; 209 static setFuncFrame( 210 funcNode: FuncStruct, 211 padding: number, 212 startNS: number, 213 endNS: number, 214 totalNS: number, 215 frame: Rect 216 ): void { 217 let x1: number; 218 let x2: number; 219 if ((funcNode.startTs || 0) > startNS && (funcNode.startTs || 0) <= endNS) { 220 x1 = ns2x(funcNode.startTs || 0, startNS, endNS, totalNS, frame); 221 } else { 222 x1 = 0; 223 } 224 if ( 225 (funcNode.startTs || 0) + (funcNode.dur || 0) > startNS && 226 (funcNode.startTs || 0) + (funcNode.dur || 0) <= endNS 227 ) { 228 x2 = ns2x((funcNode.startTs || 0) + (funcNode.dur || 0), startNS, endNS, totalNS, frame); 229 } else { 230 x2 = frame.width; 231 } 232 if (!funcNode.frame) { 233 funcNode.frame = new Rect(0, 0, 0, 0); 234 } 235 let getV: number = x2 - x1 < 1 ? 1 : x2 - x1; 236 funcNode.frame.x = Math.floor(x1); 237 funcNode.frame.y = funcNode.depth! * 18 + 3; 238 funcNode.frame.width = Math.ceil(getV); 239 funcNode.frame.height = 18; 240 } 241 242 static draw(ctx: CanvasRenderingContext2D, data: FuncStruct, flagConfig?: unknown): void { 243 if (data.frame) { 244 if (data.dur === undefined || data.dur === null) { 245 } else { 246 ctx.globalAlpha = 1; 247 //@ts-ignore 248 if (Utils.getInstance().getCallStatckMap().get(data.funName) !== undefined) { 249 //@ts-ignore 250 ctx.fillStyle = ColorUtils.FUNC_COLOR[Utils.getInstance().getCallStatckMap().get(data.funName)]; 251 //@ts-ignore 252 this.textColor = ColorUtils.FUNC_COLOR[Utils.getInstance().getCallStatckMap().get(data.funName)]; 253 } else { 254 ctx.fillStyle = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; 255 this.textColor = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; 256 } 257 if (FuncStruct.hoverFuncStruct && data.funName === FuncStruct.hoverFuncStruct.funName) { 258 ctx.globalAlpha = 0.7; 259 } 260 ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height); 261 if (data.frame.width > 10) { 262 ctx.fillStyle = ColorUtils.funcTextColor(this.textColor); 263 ctx.textBaseline = 'middle'; 264 drawFunString(ctx, `${data.funName || ''}`, 5, data.frame, data); 265 } 266 if ( 267 data.callid === FuncStruct.selectFuncStruct?.callid && 268 data.startTs === FuncStruct.selectFuncStruct?.startTs && 269 data.depth === FuncStruct.selectFuncStruct?.depth 270 ) { 271 ctx.strokeStyle = '#000'; 272 ctx.lineWidth = 2; 273 ctx.strokeRect(data.frame.x, data.frame.y + 1, data.frame.width, data.frame.height - 2); 274 } 275 //@ts-ignore 276 if (flagConfig!.TaskPool === 'Enabled') { 277 if (data.funName!.indexOf('H:Task PerformTask End:') >= 0 && data.funName!.indexOf('Successful') < 0) { 278 if (data.frame!.width < 10) { 279 FuncStruct.drawTaskPoolUnSuccessFlag(ctx, data.frame!.x, (data.depth! + 0.5) * 18, 3, data!); 280 } else { 281 FuncStruct.drawTaskPoolUnSuccessFlag(ctx, data.frame!.x, (data.depth! + 0.5) * 18, 6, data!); 282 } 283 } 284 if (data.funName!.indexOf('H:Thread Timeout Exit') >= 0) { 285 FuncStruct.drawTaskPoolTimeOutFlag(ctx, data.frame!.x, (data.depth! + 0.5) * 18, 10, data!); 286 } 287 } 288 // 如果该函数没有结束时间,则绘制锯齿。 289 if (data.nofinish && data.frame!.width > 4) { 290 FuncStruct.drawRupture(ctx, data.frame.x, data.frame.y, data.frame.width, data.frame.height); 291 } 292 } 293 } 294 } 295 296 /** 297 * 绘制锯齿 298 * @param ctx 绘图上下文环境 299 * @param x 水平坐标 300 * @param y 垂直坐标 301 * @param width 函数矩形框的宽度 302 * @param height 函数矩形框的高度 303 */ 304 static drawRupture(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number): void { 305 ctx.fillStyle = '#fff'; // 白色: '#fff' , 红色: '#FF0000'; 306 let ruptureWidth = 5; 307 let ruptureNode = height / ruptureWidth; 308 let len = height / ruptureNode; 309 ctx.moveTo(x + width - 1, y); 310 for (let i = 1; i <= ruptureNode; i++) { 311 ctx.lineTo(x + width - 1 - (i % 2 === 0 ? 0 : ruptureWidth), y + len * i - 2); 312 } 313 ctx.closePath(); 314 ctx.fill(); 315 } 316 317 static drawTaskPoolUnSuccessFlag( 318 ctx: CanvasRenderingContext2D, 319 x: number, 320 y: number, 321 radius: number, 322 data: FuncStruct 323 ): void { 324 ctx.strokeStyle = '#FFC880'; 325 ctx.lineWidth = 1; 326 ctx.beginPath(); 327 ctx.arc(x + data.frame!.width, y, radius, 0, Math.PI * 2); 328 ctx.closePath(); 329 ctx.fillStyle = '#E64566'; 330 ctx.fill(); 331 ctx.stroke(); 332 } 333 334 static drawTaskPoolTimeOutFlag( 335 canvas: CanvasRenderingContext2D, 336 x: number, 337 y: number, 338 radius: number, 339 data: FuncStruct 340 ): void { 341 canvas.strokeStyle = '#FFC880'; 342 canvas.lineWidth = 1; 343 canvas.beginPath(); 344 canvas.arc(x + data.frame!.width + 20, y, radius, 0, Math.PI * 2); 345 canvas.closePath(); 346 canvas.fillStyle = '#FFC880'; 347 canvas.fill(); 348 canvas.stroke(); 349 canvas.font = '18px Arial'; 350 canvas.fillStyle = ColorUtils.GREY_COLOR; 351 canvas.textAlign = 'center'; 352 canvas.fillText('¡', x + data.frame!.width + 20, y); 353 } 354 355 static isSelected(data: FuncStruct): boolean { 356 return ( 357 FuncStruct.selectFuncStruct !== undefined && 358 FuncStruct.selectFuncStruct.startTs === data.startTs && 359 FuncStruct.selectFuncStruct.depth === data.depth 360 ); 361 } 362} 363