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