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 { BaseStruct, drawLoadingFrame, isFrameContainPoint, ns2x, Rect, Render } from './ProcedureWorkerCommon'; 17import { TraceRow } from '../../component/trace/base/TraceRow'; 18import { SpSystemTrace } from '../../component/SpSystemTrace'; 19import { ColorUtils } from '../../component/trace/base/ColorUtils'; 20 21export class XpowerGpuFreqRender extends Render { 22 renderMainThread( 23 xpowerStasticReq: { 24 context: CanvasRenderingContext2D; 25 useCache: boolean; 26 }, 27 row: TraceRow<XpowerGpuFreqStruct> 28 ): void { 29 // offsetW控制图例的横向偏移量 确保图例不超过画布边界 因收藏和非收藏时泳道的宽度不一致 offsetW根据情况调整 30 let offsetW: number = row.collect ? 160 : 400; 31 let checkedType = row.rowSettingCheckedBoxList; 32 let checkedValue = row.rowSettingCheckBoxList; 33 checkedValue!.forEach((item, index) => { 34 if (!XpowerGpuFreqStruct.colorMap.has(Number(item))) { 35 XpowerGpuFreqStruct.colorMap.set(Number(item), ColorUtils.MD_PALETTE[index]); 36 } 37 }); 38 let xpowerGpuFreqList = row.dataListCache.filter( 39 // @ts-ignore 40 (item) => checkedType[checkedValue?.indexOf(item.frequency.toString())] 41 ); 42 // 根据startNS分组 43 const groupedData = new Map(); 44 xpowerGpuFreqList.forEach((item) => { 45 if(!groupedData.has(item.startNS)){ 46 groupedData.set(item.startNS,[]); 47 } 48 groupedData.get(item.startNS).push(item); 49 }); 50 // 对直方图每个组内数据进行排序 51 const sortedData: any = []; 52 groupedData.forEach(group => { 53 group.sort((a:any,b:any) => a.frequency - b.frequency); 54 sortedData.push(...group); 55 }); 56 // 对鼠标悬浮框每个组内数据排序 57 const tooltipData: any = []; 58 groupedData.forEach(group => { 59 group.sort((a:any,b:any) => b.frequency - a.frequency); 60 tooltipData.push(...group); 61 }); 62 xpowerGpuFreqList = sortedData; 63 let xpowerMap = new Map<number, XpowerGpuFreqStruct[]>(); 64 setGroupByTime(xpowerMap, xpowerGpuFreqList); 65 XpowerGpuFreqStruct.xpowerMap = xpowerMap; 66 setDataFrameAndHoverHtml(tooltipData, row); 67 drawLoadingFrame(xpowerStasticReq.context, xpowerGpuFreqList, row); 68 setMaxEnergyInfo(xpowerStasticReq.context, xpowerMap); 69 70 xpowerStasticReq.context.beginPath(); 71 let find = false; 72 for (let re of xpowerGpuFreqList) { 73 XpowerGpuFreqStruct.draw(xpowerStasticReq, re, row, xpowerGpuFreqList.length); 74 if (row.isHover && re.frame && isFrameContainPoint(re.frame, row.hoverX, row.hoverY)) { 75 XpowerGpuFreqStruct.hoverXpowerStruct = re; 76 find = true; 77 } 78 } 79 if (!find) { 80 XpowerGpuFreqStruct.hoverXpowerStruct = undefined; 81 } 82 xpowerStasticReq.context.closePath(); 83 let spApplication = document.getElementsByTagName('sp-application')[0]; 84 let isDark = spApplication && spApplication.hasAttribute('dark'); 85 drawLegend(xpowerStasticReq, checkedType!, checkedValue!, offsetW, isDark); 86 } 87} 88 89function setGroupByTime(xpowerMap: Map<number, XpowerGpuFreqStruct[]>, xpowerGpuFreqList: XpowerGpuFreqStruct[]): void { 90 xpowerGpuFreqList.forEach((item, index) => { 91 if (xpowerMap.has(item.startNS)) { 92 let data = xpowerMap.get(item.startNS); 93 data!.push(item); 94 xpowerMap.set(item.startNS, data!); 95 } else { 96 xpowerMap.set(item.startNS, []); 97 let data = xpowerMap.get(item.startNS); 98 data!.push(item); 99 xpowerMap.set(item.startNS, data!); 100 } 101 }); 102} 103 104function setDataFrameAndHoverHtml(filter: XpowerGpuFreqStruct[], row: TraceRow<XpowerGpuFreqStruct>): void { 105 filter.forEach((item) => { 106 XpowerGpuFreqStruct.setXPowerGpuFreqFrame( 107 item, 108 5, 109 TraceRow.range?.startNS ?? 0, 110 TraceRow.range?.endNS ?? 0, 111 TraceRow.range?.totalNS ?? 0, 112 row.frame 113 ); 114 }); 115 let hoverMap: Map<number, string> = new Map(); 116 filter.forEach((item) => { 117 if (hoverMap.has(item.startNS)) { 118 hoverMap.set(item.startNS, hoverMap.get(item.startNS) + item.hoverHtml); 119 } else { 120 hoverMap.set(item.startNS, item.hoverHtml); 121 } 122 }); 123 XpowerGpuFreqStruct.hoverMap = hoverMap; 124} 125 126function setMaxEnergyInfo(context: CanvasRenderingContext2D, xpowerMap: Map<number, XpowerGpuFreqStruct[]>): void { 127 XpowerGpuFreqStruct.computeMaxEnergy(xpowerMap); 128 let s = XpowerGpuFreqStruct.max + ' ms'; 129 let textMetrics = context.measureText(s); 130 context.globalAlpha = 0.8; 131 context.fillStyle = '#f0f0f0'; 132 context.fillRect(0, 5, textMetrics.width + 8, 18); 133 context.globalAlpha = 1; 134 context.fillStyle = '#333'; 135 context.textBaseline = 'middle'; 136 context.fillText(s, 4, 5 + 9); 137} 138 139export function drawLegend( 140 req: { context: CanvasRenderingContext2D; useCache: boolean }, 141 checked: boolean[], 142 checkedValue: string[], 143 offsetW: number, 144 isDark?: boolean 145): void { 146 let textList: string[] = []; 147 checkedValue.forEach((item, index) => { 148 if (checked[index]) { 149 textList.push(item.toUpperCase()); 150 } 151 }); 152 for (let index = 0; index < textList.length; index++) { 153 let text = req.context.measureText(textList[index]); 154 req.context.fillStyle = XpowerGpuFreqStruct.colorMap.get(Number(textList[index]))!; 155 req.context.globalAlpha = 1; 156 let canvasEndX = req.context.canvas.clientWidth - offsetW; 157 let textColor = isDark ? '#FFFFFF' : '#333'; 158 if (index === 0) { 159 req!.context.fillRect(canvasEndX - textList.length * 80, 12, 8, 8); 160 req.context.globalAlpha = 0.8; 161 req.context.fillStyle = textColor; 162 req.context.textBaseline = 'middle'; 163 req.context.fillText(textList[index], canvasEndX - textList.length * 80 + 10, 18); 164 XpowerGpuFreqStruct.currentTextWidth = canvasEndX - textList.length * 80 + 40 + text.width; 165 } else { 166 req!.context.fillRect(XpowerGpuFreqStruct.currentTextWidth, 12, 8, 8); 167 req.context.globalAlpha = 0.8; 168 req.context.fillStyle = textColor; 169 req.context.textBaseline = 'middle'; 170 req!.context.fillText(textList[index], XpowerGpuFreqStruct.currentTextWidth + 12, 18); 171 XpowerGpuFreqStruct.currentTextWidth = XpowerGpuFreqStruct.currentTextWidth + 40 + text.width; 172 } 173 } 174 req.context.fillStyle = '#333'; 175} 176 177export function XpowerGpuFreqStructOnClick( 178 clickRowType: string, 179 sp: SpSystemTrace, 180 entry?: XpowerGpuFreqStruct 181): Promise<unknown> { 182 return new Promise((resolve, reject) => { 183 if (clickRowType === TraceRow.ROW_TYPE_XPOWER_GPU_FREQUENCY && (XpowerGpuFreqStruct.hoverXpowerStruct || entry)) { 184 XpowerGpuFreqStruct.selectXpowerStruct = entry || XpowerGpuFreqStruct.hoverXpowerStruct; 185 let startNs = XpowerGpuFreqStruct.selectXpowerStruct!.startNS; 186 XpowerGpuFreqStruct.xpowerMap.get(startNs)!.length > 0 && 187 sp.traceSheetEL?.displayXpowerGpuFreqData(XpowerGpuFreqStruct.xpowerMap.get(startNs) || []); 188 sp.timerShaftEL?.modifyFlagList(undefined); 189 reject(new Error()); 190 } else { 191 resolve(null); 192 } 193 }); 194} 195 196export class XpowerGpuFreqStruct extends BaseStruct { 197 static rowHeight: number = 200; 198 static currentTextWidth: number = 0; 199 static hoverXpowerStruct: XpowerGpuFreqStruct | undefined; 200 static selectXpowerStruct: XpowerGpuFreqStruct | undefined; 201 202 startNS: number = 0; 203 startMS: number = 0; 204 runTime: number = 0; 205 idleTime: number = 0; 206 frequency: number = 0; 207 count: number = 0; 208 static max: number = 0; 209 hoverHtml: string = ''; 210 211 static xpowerMap = new Map<number, XpowerGpuFreqStruct[]>(); 212 static hoverMap: Map<number, string> = new Map(); 213 static histogramHeightMap = new Map<number, number>(); 214 215 static flagTime: number = 0; 216 static height: number = -1; 217 static drawY: number = 0; 218 static colorMap: Map<number, string> = new Map(); 219 static gpuFreqStructMap = new Map<number, Array<XpowerGpuFreqStruct>>(); 220 221 static draw( 222 req: { useCache: boolean; context: CanvasRenderingContext2D }, 223 data: XpowerGpuFreqStruct, 224 row: TraceRow<XpowerGpuFreqStruct>, 225 length: number 226 ): void { 227 if (data.frame) { 228 req.context.globalAlpha = 0.8; 229 if (data.startNS !== XpowerGpuFreqStruct.flagTime || length === 1) { 230 this.height = -1; 231 } else { 232 this.height = this.drawY; 233 } 234 XpowerGpuFreqStruct.flagTime = data.startNS; 235 this.drawY = this.drawHistogram(req, data, row.frame); 236 } 237 XpowerGpuFreqStruct.drawStroke(req, data, row); 238 } 239 240 static equals(baseStruct: XpowerGpuFreqStruct, targetStruct: XpowerGpuFreqStruct): boolean { 241 return baseStruct === targetStruct; 242 } 243 244 static drawStroke( 245 req: { useCache: boolean; context: CanvasRenderingContext2D }, 246 data: XpowerGpuFreqStruct, 247 row: TraceRow<XpowerGpuFreqStruct> 248 ): void { 249 let startNS = TraceRow.range!.startNS; 250 let endNS = TraceRow.range!.endNS; 251 let totalNS = TraceRow.range!.totalNS; 252 if ( 253 XpowerGpuFreqStruct.equals(XpowerGpuFreqStruct.hoverXpowerStruct!, data) || 254 XpowerGpuFreqStruct.equals(XpowerGpuFreqStruct.selectXpowerStruct!, data) 255 ) { 256 let startPointX = ns2x(data.startNS || 0, startNS, endNS, totalNS, row.frame); 257 let endPointX = ns2x((data.startNS || 0) + 3000000000, startNS, endNS, totalNS, row.frame); 258 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 259 req!.context.lineWidth = 1; 260 req!.context.strokeStyle = '#9899a0'; 261 let height = this.histogramHeightMap.get(data.startNS)! || 0; 262 req!.context.strokeRect(startPointX, this.rowHeight - height, Math.ceil(frameWidth), height + 1); 263 } 264 } 265 266 static drawHoverFrame( 267 data: XpowerGpuFreqStruct, 268 isHover: boolean, 269 wifiHeight: number, 270 req: { context: CanvasRenderingContext2D; useCache: boolean }, 271 row: TraceRow<XpowerGpuFreqStruct> 272 ): void { 273 let startNS = TraceRow.range!.startNS; 274 let endNS = TraceRow.range!.endNS; 275 let totalNS = TraceRow.range!.totalNS; 276 if ( 277 (data.startNS === XpowerGpuFreqStruct.hoverXpowerStruct?.startNS && isHover) || 278 (XpowerGpuFreqStruct.selectXpowerStruct && data.startNS === XpowerGpuFreqStruct.selectXpowerStruct?.startNS) 279 ) { 280 let endPointX = ns2x((data.startNS || 0) + 3000000000, startNS, endNS, totalNS, row.frame); 281 let startPointX = ns2x(data.startNS || 0, startNS, endNS, totalNS, row.frame); 282 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 283 req.context.globalAlpha = 1; 284 req!.context.lineWidth = 2; 285 req.context.strokeStyle = '#9899a0'; 286 req!.context.strokeRect(startPointX, wifiHeight, frameWidth, data.frame!.height); 287 } 288 } 289 290 static drawHistogram( 291 req: { useCache: boolean; context: CanvasRenderingContext2D }, 292 data: XpowerGpuFreqStruct, 293 rowFrame: Rect 294 ): number { 295 let endPointX = Math.ceil( 296 ns2x( 297 (data.startNS || 0) + 3000000000, 298 TraceRow.range!.startNS, 299 TraceRow.range!.endNS, 300 TraceRow.range!.totalNS, 301 rowFrame 302 ) 303 ); 304 let startPointX = Math.ceil( 305 ns2x(data.startNS || 0, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, rowFrame) 306 ); 307 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 308 let histogramColor = this.colorMap.get(data.frequency)!; 309 req!.context.fillStyle = histogramColor; 310 let drawStartY = 0; 311 let dataHeight: number = ((data.runTime || 0) * (this.rowHeight - 40)) / XpowerGpuFreqStruct.max; 312 313 if (data.runTime !== 0 && dataHeight < 1) { 314 dataHeight = 1; 315 } 316 data.frame!.x = startPointX; 317 data.frame!.width = frameWidth; 318 data.frame!.height = dataHeight; 319 if (this.height === -1) { 320 drawStartY = this.rowHeight - dataHeight; 321 data.frame!.y = drawStartY; 322 req!.context.fillRect(startPointX, drawStartY, Math.floor(frameWidth), dataHeight); 323 return drawStartY; 324 } else { 325 drawStartY = this.height - dataHeight; 326 data.frame!.y = drawStartY; 327 req!.context.fillRect(startPointX, drawStartY, Math.floor(frameWidth), dataHeight); 328 return drawStartY; 329 } 330 } 331 332 static computeMaxEnergy(map: Map<number, XpowerGpuFreqStruct[]>): void { 333 let maxRunTime = 0; 334 map.forEach((list, key) => { 335 let total = 0; 336 list.forEach((item) => { 337 total += item.runTime; 338 }); 339 if (maxRunTime < total) { 340 maxRunTime = total; 341 } 342 let mapValue = Math.ceil(((total || 0) * (XpowerGpuFreqStruct.rowHeight - 28)) / XpowerGpuFreqStruct.max); 343 XpowerGpuFreqStruct.histogramHeightMap.set(list[0].startNS, mapValue); 344 }); 345 XpowerGpuFreqStruct.max = maxRunTime; 346 } 347 348 static setXPowerGpuFreqFrame( 349 node: XpowerGpuFreqStruct, 350 padding: number, 351 startNS: number, 352 endNS: number, 353 totalNS: number, 354 frame: Rect 355 ): void { 356 let startPointX: number; 357 let endPointX: number; 358 if ((node.startNS || 0) < startNS) { 359 startPointX = 0; 360 } else { 361 startPointX = ns2x(node.startNS, startNS, endNS, totalNS, frame); 362 } 363 if (node.startNS + 3000000000 > endNS) { 364 endPointX = frame.width; 365 } else { 366 endPointX = ns2x(node.startNS + 3000000000, startNS, endNS, totalNS, frame); 367 } 368 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 369 if (!node.frame) { 370 node.frame = new Rect(0, 0, 0, 0); 371 } 372 node.frame.x = Math.floor(startPointX); 373 node.frame.y = frame.y + padding; 374 node.frame.width = Math.ceil(frameWidth); 375 node.frame.height = Math.floor(frame.height - padding * 2); 376 } 377} 378