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 { 18 BaseStruct, 19 dataFilterHandler, 20 drawFlagLine, 21 drawLines, 22 drawLoadingFrame, 23 drawSelection, 24 drawWakeUp, 25 drawWakeUpList, 26 Rect, 27 Render, 28 RequestMessage, 29} from '../ProcedureWorkerCommon'; 30import { TraceRow } from '../../../component/trace/base/TraceRow'; 31import { SpSystemTrace } from '../../../component/SpSystemTrace'; 32import { Utils } from '../../../component/trace/base/Utils'; 33 34export class EmptyRender extends Render { 35 //@ts-ignore 36 renderMainThread(req: unknown, row: TraceRow<unknown>): void { 37 //@ts-ignore 38 drawLoadingFrame(req.context, [], row); 39 } 40 render(cpuReqMessage: RequestMessage, list: Array<CpuStruct>, filter: Array<CpuStruct>): void { 41 if (cpuReqMessage.canvas) { 42 cpuReqMessage.context.clearRect(0, 0, cpuReqMessage.frame.width, cpuReqMessage.frame.height); 43 cpuReqMessage.context.beginPath(); 44 drawLines(cpuReqMessage.context, cpuReqMessage.xs!, cpuReqMessage.frame.height, cpuReqMessage.lineColor); 45 drawSelection(cpuReqMessage.context, cpuReqMessage.params); 46 cpuReqMessage.context.closePath(); 47 drawFlagLine( 48 cpuReqMessage.context, 49 cpuReqMessage.flagMoveInfo!, 50 cpuReqMessage.flagSelectedInfo!, 51 cpuReqMessage.startNS, 52 cpuReqMessage.endNS, 53 cpuReqMessage.totalNS, 54 cpuReqMessage.frame, 55 cpuReqMessage.slicesTime 56 ); 57 } 58 // @ts-ignore 59 self.postMessage({ 60 id: cpuReqMessage.id, 61 type: cpuReqMessage.type, 62 results: cpuReqMessage.canvas ? undefined : filter, 63 hover: null, 64 }); 65 } 66} 67 68export class CpuRender { 69 renderMainThread( 70 req: { 71 ctx: CanvasRenderingContext2D; 72 useCache: boolean; 73 type: string; 74 translateY: number; 75 }, 76 row: TraceRow<CpuStruct> 77 ): void { 78 let cpuList = row.dataList; 79 let cpuFilter = row.dataListCache; 80 let startNS = TraceRow.range!.startNS ?? 0; 81 let endNS = TraceRow.range!.endNS ?? 0; 82 let totalNS = TraceRow.range!.totalNS ?? 0; 83 dataFilterHandler(cpuList, cpuFilter, { 84 startKey: 'startTime', 85 durKey: 'dur', 86 startNS: startNS, 87 endNS: endNS, 88 totalNS: totalNS, 89 frame: row.frame, 90 paddingTop: 3, 91 useCache: req.useCache || !(TraceRow.range?.refresh ?? false), 92 }); 93 drawLoadingFrame(req.ctx, cpuFilter, row); 94 req.ctx.beginPath(); 95 req.ctx.font = '11px sans-serif'; 96 cpuFilter.forEach((re) => { 97 re.translateY = req.translateY; 98 CpuStruct.draw(req.ctx, re, req.translateY); 99 }); 100 req.ctx.closePath(); 101 if ((row.traceId === Utils.currentSelectTrace) || (row.traceId === null && Utils.currentSelectTrace === undefined)) { 102 let currentCpu = parseInt(req.type!.replace('cpu-data-', '')); 103 let wakeup = req.type === `cpu-data-${CpuStruct.selectCpuStruct?.cpu || 0}` ? 104 CpuStruct.selectCpuStruct : undefined; 105 drawWakeUp(req.ctx, CpuStruct.wakeupBean, startNS, endNS, totalNS, row.frame, wakeup, currentCpu, true); 106 for (let i = 0; i < SpSystemTrace.wakeupList.length; i++) { 107 if (i + 1 === SpSystemTrace.wakeupList.length) { 108 return; 109 } 110 let wake = SpSystemTrace.wakeupList[i + 1]; 111 let wakeupListItem = 112 req.type === `cpu-data-${SpSystemTrace.wakeupList[i]?.cpu || 0}` ? SpSystemTrace.wakeupList[i] : undefined; 113 drawWakeUpList(req.ctx, wake, startNS, endNS, totalNS, row.frame, wakeupListItem, currentCpu, true); 114 } 115 } 116 } 117 118 cpu( 119 cpuList: Array<CpuStruct>, 120 cpuRes: Array<CpuStruct>, 121 startNS: number, 122 endNS: number, 123 totalNS: number, 124 frame: Rect, 125 use: boolean 126 ): void { 127 if (use && cpuRes.length > 0) { 128 this.setFrameCpuByRes(cpuRes, startNS, endNS, frame); 129 return; 130 } 131 if (cpuList) { 132 this.setFrameCpuByList(cpuRes, startNS, endNS, frame, cpuList); 133 } 134 } 135 136 setFrameCpuByRes(cpuRes: Array<CpuStruct>, startNS: number, endNS: number, frame: Rect): void { 137 let pns = (endNS - startNS) / frame.width; 138 let y = frame.y + 5; 139 let height = frame.height - 10; 140 for (let i = 0, len = cpuRes.length; i < len; i++) { 141 let it = cpuRes[i]; 142 if ((it.startTime || 0) + (it.dur || 0) > startNS && (it.startTime || 0) < endNS) { 143 if (!cpuRes[i].frame) { 144 cpuRes[i].frame = new Rect(0, 0, 0, 0); 145 cpuRes[i].frame!.y = y; 146 cpuRes[i].frame!.height = height; 147 } 148 CpuStruct.setCpuFrame(cpuRes[i], pns, startNS, endNS, frame); 149 } else { 150 cpuRes[i].frame = undefined; 151 } 152 } 153 } 154 155 setFrameCpuByList( cpuRes: Array<CpuStruct>, startNS: number, endNS: number, frame: Rect, 156 cpuList: Array<CpuStruct>): void { 157 cpuRes.length = 0; 158 let pns = (endNS - startNS) / frame.width; //每个像素多少ns 159 let y = frame.y + 5; 160 let height = frame.height - 10; 161 let left = 0; 162 let right = 0; 163 for (let i = 0, j = cpuList.length - 1, ib = true, jb = true; i < cpuList.length, j >= 0; i++, j--) { 164 if (cpuList[j].startTime! <= endNS && jb) { 165 right = j; 166 jb = false; 167 } 168 if (cpuList[i].startTime! + cpuList[i].dur! >= startNS && ib) { 169 left = i; 170 ib = false; 171 } 172 if (!ib && !jb) { 173 break; 174 } 175 } 176 let slice = cpuList.slice(left, right + 1); 177 let sum = 0; 178 for (let i = 0; i < slice.length; i++) { 179 if (!slice[i].frame) { 180 slice[i].frame = new Rect(0, 0, 0, 0); 181 slice[i].frame!.y = y; 182 slice[i].frame!.height = height; 183 } 184 if (slice[i].dur! >= pns) { 185 slice[i].v = true; 186 CpuStruct.setCpuFrame(slice[i], pns, startNS, endNS, frame); 187 } else { 188 if (i > 0) { 189 let c = slice[i].startTime! - slice[i - 1].startTime! - slice[i - 1].dur!; 190 if (c < pns && sum < pns) { 191 sum += c + slice[i - 1].dur!; 192 slice[i].v = false; 193 } else { 194 slice[i].v = true; 195 CpuStruct.setCpuFrame(slice[i], pns, startNS, endNS, frame); 196 sum = 0; 197 } 198 } 199 } 200 } 201 cpuRes.push(...slice.filter((it) => it.v)); 202 } 203} 204export function CpuStructOnClick( 205 rowType: string, 206 sp: SpSystemTrace, 207 cpuClickHandler: unknown, 208 entry?: CpuStruct, 209): Promise<unknown> { 210 return new Promise((resolve, reject) => { 211 if (rowType === TraceRow.ROW_TYPE_CPU && (CpuStruct.hoverCpuStruct || entry)) { 212 CpuStruct.selectCpuStruct = entry || CpuStruct.hoverCpuStruct; 213 sp.timerShaftEL?.drawTriangle(CpuStruct.selectCpuStruct!.startTime || 0, 'inverted'); 214 sp.traceSheetEL?.displayCpuData( 215 CpuStruct.selectCpuStruct!, 216 (wakeUpBean) => { 217 CpuStruct.wakeupBean = wakeUpBean; 218 sp.refreshCanvas(false); 219 }, 220 //@ts-ignore 221 cpuClickHandler 222 ); 223 sp.timerShaftEL?.modifyFlagList(undefined); 224 reject(new Error()); 225 } else { 226 resolve(null); 227 } 228 }); 229} 230export class CpuStruct extends BaseStruct { 231 static hoverCpuStruct: CpuStruct | undefined; 232 static selectCpuStruct: CpuStruct | undefined; 233 static wakeupBean: WakeupBean | null | undefined = null; 234 cpu: number | undefined; 235 dur: number | undefined; 236 end_state: string | undefined; 237 state: string | undefined; 238 id: number | undefined; 239 tid: number | undefined; 240 name: string | undefined; 241 priority: number | undefined; 242 processCmdLine: string | undefined; 243 processId: number | undefined; 244 processName: string | undefined; 245 displayProcess: string | undefined; 246 displayThread: string | undefined; 247 measurePWidth: number = 0; 248 measureTWidth: number = 0; 249 startTime: number | undefined; 250 argSetID: number | undefined; 251 type: string | undefined; 252 v: boolean = false; 253 nofinish: boolean = false; 254 ts: number | undefined; 255 itid: number | undefined; 256 process: string | undefined; 257 pid: number | undefined; 258 thread: string | undefined; 259 isKeyPath?: number; 260 261 static draw(ctx: CanvasRenderingContext2D, data: CpuStruct, translateY: number): void { 262 if (data.frame) { 263 let pid = data.processId || 0; 264 let tid = data.tid || 0; 265 let width = data.frame.width || 0; 266 if (data.tid === CpuStruct.hoverCpuStruct?.tid || !CpuStruct.hoverCpuStruct) { 267 ctx.globalAlpha = 1; 268 ctx.fillStyle = ColorUtils.colorForTid(pid > 0 ? pid : tid); 269 } else if (data.processId === CpuStruct.hoverCpuStruct?.processId) { 270 ctx.globalAlpha = 0.6; 271 ctx.fillStyle = ColorUtils.colorForTid(pid > 0 ? pid : tid); 272 } else { 273 ctx.globalAlpha = 1; 274 ctx.fillStyle = '#e0e0e0'; 275 } 276 ctx.fillRect(data.frame.x, data.frame.y, width, data.frame.height); 277 ctx.globalAlpha = 1; 278 CpuStruct.drawText(ctx, data, width, pid, tid); 279 CpuStruct.drawRim(ctx, data, width); 280 } 281 } 282 static drawText(ctx: CanvasRenderingContext2D, data: CpuStruct, width: number, pid: number, tid: number): void { 283 let textFillWidth = width - textPadding * 2; 284 if (data.frame && textFillWidth > 3) { 285 if (data.displayProcess === undefined) { 286 data.displayProcess = `${data.processName || 'Process'} [${data.processId}]`; 287 data.measurePWidth = ctx.measureText(data.displayProcess).width; 288 } 289 if (data.displayThread === undefined) { 290 data.displayThread = `${data.name || 'Thread'} [${data.tid}] [Prio:${data.priority || 0}]`; 291 data.measureTWidth = ctx.measureText(data.displayThread).width; 292 } 293 let processCharWidth = Math.round(data.measurePWidth / data.displayProcess.length); 294 let threadCharWidth = Math.round(data.measureTWidth / data.displayThread.length); 295 ctx.fillStyle = ColorUtils.funcTextColor(ColorUtils.colorForTid(pid > 0 ? pid : tid)); 296 ctx.font = '9px sans-serif'; 297 ctx.textBaseline = 'bottom'; 298 let y = data.frame.height / 2 + data.frame.y; 299 if (data.measurePWidth < textFillWidth) { 300 let x1 = Math.floor(width / 2 - data.measurePWidth / 2 + data.frame.x + textPadding); 301 ctx.fillText(data.displayProcess, x1, y, textFillWidth); 302 } else { 303 if (textFillWidth >= processCharWidth) { 304 let chatNum = textFillWidth / processCharWidth; 305 let x1 = data.frame.x + textPadding; 306 if (chatNum < 2) { 307 ctx.fillText(data.displayProcess.substring(0, 1), x1, y, textFillWidth); 308 } else { 309 ctx.fillText(`${data.displayProcess.substring(0, chatNum - 1)}...`, x1, y, textFillWidth); 310 } 311 } 312 } 313 ctx.textBaseline = 'top'; 314 if (data.measureTWidth < textFillWidth) { 315 let x2 = Math.floor(width / 2 - data.measureTWidth / 2 + data.frame.x + textPadding); 316 ctx.fillText(data.displayThread, x2, y + 2, textFillWidth); 317 } else { 318 if (textFillWidth >= threadCharWidth) { 319 let chatNum = textFillWidth / threadCharWidth; 320 let x1 = data.frame.x + textPadding; 321 if (chatNum < 2) { 322 ctx.fillText(data.displayThread.substring(0, 1), x1, y + 2, textFillWidth); 323 } else { 324 ctx.fillText( `${data.displayThread.substring(0, chatNum - 1)}...`, x1, y + 2, textFillWidth); 325 } 326 } 327 } 328 } 329 } 330 static drawRim(ctx: CanvasRenderingContext2D, data: CpuStruct, width: number): void { 331 if (data.frame) { 332 if (data.nofinish && width > 4) { 333 ctx.fillStyle = '#fff'; 334 let ruptureWidth = 4; 335 let ruptureNode = 8; 336 ctx.moveTo(data.frame.x + data.frame.width - 1, data.frame.y); 337 for (let i = 1; i <= ruptureNode; i++) { 338 ctx.lineTo( 339 data.frame.x + data.frame.width - 1 - (i % 2 === 0 ? 0 : ruptureWidth), 340 data.frame.y + (data.frame.height / ruptureNode) * i 341 ); 342 } 343 ctx.closePath(); 344 ctx.fill(); 345 } 346 if (data.isKeyPath) { 347 const gradient = ctx.createLinearGradient(0, 0, 0, 40); 348 gradient.addColorStop(0, '#000000'); 349 gradient.addColorStop(0.5, '#0000FF'); 350 gradient.addColorStop(1, '#FF0000'); 351 ctx.strokeStyle = gradient; 352 ctx.lineWidth = 4; 353 ctx.strokeRect(data.frame.x, data.frame.y - 2, width - 2, data.frame.height + 2); 354 } 355 if (CpuStruct.selectCpuStruct && CpuStruct.equals(CpuStruct.selectCpuStruct, data)) { 356 ctx.strokeStyle = '#232c5d'; 357 ctx.lineWidth = 2; 358 ctx.strokeRect(data.frame.x, data.frame.y, width - 2, data.frame.height); 359 } 360 } 361 } 362 363 static setCpuFrame(cpuNode: CpuStruct, pns: number, startNS: number, endNS: number, frame: Rect): void { 364 if (!cpuNode.frame) { 365 return; 366 } 367 if ((cpuNode.startTime || 0) < startNS) { 368 cpuNode.frame.x = 0; 369 } else { 370 cpuNode.frame.x = Math.floor(((cpuNode.startTime || 0) - startNS) / pns); 371 } 372 if ((cpuNode.startTime || 0) + (cpuNode.dur || 0) > endNS) { 373 cpuNode.frame.width = frame.width - cpuNode.frame.x; 374 } else { 375 cpuNode.frame.width = Math.ceil( 376 ((cpuNode.startTime || 0) + (cpuNode.dur || 0) - startNS) / pns - cpuNode.frame.x 377 ); 378 } 379 if (cpuNode.frame.width < 1) { 380 cpuNode.frame.width = 1; 381 } 382 } 383 384 static equals(d1: CpuStruct, d2: CpuStruct): boolean { 385 return ( 386 d1 && 387 d2 && 388 d1.cpu === d2.cpu && 389 d1.tid === d2.tid && 390 d1.processId === d2.processId && 391 d1.startTime === d2.startTime && 392 d1.dur === d2.dur 393 ); 394 } 395} 396 397export class WakeupBean { 398 wakeupTime: number | undefined; 399 cpu: number | undefined; 400 process: string | undefined; 401 pid: number | undefined; 402 thread: string | undefined; 403 dur: number | null | undefined; 404 tid: number | undefined; 405 schedulingLatency: number | undefined; 406 ts: number | undefined; 407 schedulingDesc: string | undefined; 408 itid: number | undefined; 409 state: string | undefined; 410 argSetID: number | undefined; 411} 412 413const textPadding = 2; 414