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 ns2x, 19 Render, 20 RequestMessage, 21 isFrameContainPoint, 22 drawLoadingFrame, 23 Rect, 24} from './ProcedureWorkerCommon'; 25import { TraceRow } from '../../component/trace/base/TraceRow'; 26 27export class EnergyPowerRender extends Render { 28 renderMainThread( 29 powerReq: { useCache: boolean; context: CanvasRenderingContext2D; type: string; appName: string }, 30 row: TraceRow<EnergyPowerStruct> 31 ): void { 32 let list = row.dataList; 33 let filter = row.dataListCache; 34 power( 35 list, 36 filter, 37 TraceRow.range!.startNS, 38 TraceRow.range!.endNS, 39 TraceRow.range!.totalNS, 40 row.frame, 41 powerReq.useCache || !TraceRow.range!.refresh, 42 powerReq.appName 43 ); 44 drawLoadingFrame(powerReq.context, row.dataListCache, row); 45 powerReq.context.beginPath(); 46 let find = false; 47 for (let i = 0; i < list.length; i++) { 48 let re = list[i]; 49 EnergyPowerStruct.draw(powerReq, i, re, row); 50 if (row.isHover && re.frame && isFrameContainPoint(re.frame, row.hoverX, row.hoverY)) { 51 EnergyPowerStruct.hoverEnergyPowerStruct = re; 52 find = true; 53 } 54 } 55 if (!find && row.isHover) { 56 EnergyPowerStruct.hoverEnergyPowerStruct = undefined; 57 } 58 TraceRow.range!.refresh = true; 59 if (EnergyPowerStruct.maxPower !== 0) { 60 let s = EnergyPowerStruct.maxPower + 'mAs'; 61 let textMetrics = powerReq.context.measureText(s); 62 powerReq.context.globalAlpha = 1.0; 63 powerReq.context.fillStyle = '#f0f0f0'; 64 powerReq.context.fillRect(0, 5, textMetrics.width + 8, 18); 65 powerReq.context.globalAlpha = 1; 66 powerReq.context.fillStyle = '#333'; 67 powerReq.context.textBaseline = 'middle'; 68 powerReq.context.fillText(s, 4, 5 + 9); 69 } 70 powerReq.context.closePath(); 71 let spApplication = document.getElementsByTagName('sp-application')[0]; 72 let isDark = spApplication.hasAttribute('dark'); 73 drawLegend(powerReq, isDark); 74 } 75} 76 77export function drawLegend( 78 req: { useCache: boolean; context: CanvasRenderingContext2D; type: string; appName: string }, 79 isDark?: boolean 80): void { 81 let textList = ['CPU', 'LOCATION', 'GPU', 'DISPLAY', 'CAMERA', 'BLUETOOTH', 'FLASHLIGHT', 'AUDIO', 'WIFISCAN']; 82 for (let index = 0; index < textList.length; index++) { 83 let text = req.context.measureText(textList[index]); 84 req.context.fillStyle = EnergyPowerStruct.getHistogramColor(textList[index]); 85 req.context.globalAlpha = 1; 86 let canvasEndX = req.context.canvas.clientWidth - EnergyPowerStruct.OFFSET_WIDTH; 87 let textColor = isDark ? '#FFFFFF' : '#333'; 88 if (index === 0) { 89 req!.context.fillRect(canvasEndX - EnergyPowerStruct.powerItemNumber * 80, 12, 8, 8); 90 req.context.globalAlpha = 1; 91 req.context.fillStyle = textColor; 92 req.context.textBaseline = 'middle'; 93 req.context.fillText(textList[index], canvasEndX - EnergyPowerStruct.powerItemNumber * 80 + 10, 18); 94 EnergyPowerStruct.currentTextWidth = canvasEndX - EnergyPowerStruct.powerItemNumber * 80 + 40 + text.width; 95 } else { 96 req!.context.fillRect(EnergyPowerStruct.currentTextWidth, 12, 8, 8); 97 req.context.globalAlpha = 1; 98 req.context.fillStyle = textColor; 99 req.context.textBaseline = 'middle'; 100 req!.context.fillText(textList[index], EnergyPowerStruct.currentTextWidth + 12, 18); 101 EnergyPowerStruct.currentTextWidth = EnergyPowerStruct.currentTextWidth + 40 + text.width; 102 } 103 } 104 req.context.fillStyle = '#333'; 105} 106 107export function power( 108 list: Array<EnergyPowerStruct>, 109 res: Array<EnergyPowerStruct>, 110 startNS: number, 111 endNS: number, 112 totalNS: number, 113 frame: Rect, 114 use: boolean, 115 appName: string 116): void { 117 EnergyPowerStruct.maxPower = 0; 118 list.length = 0; 119 let firstData = []; 120 if (use && res.length > 0) { 121 for (let index = 0; index < res.length; index++) { 122 let item = res[index]; 123 //@ts-ignore 124 let obj = item[appName]; 125 if (obj !== undefined && obj.ts + 1000000000 > (startNS || 0) && (obj.ts || 0) < (endNS || 0)) { 126 firstData.push(obj); 127 } 128 } 129 let array = firstData.sort((a, b) => a.ts - b.ts); 130 setFirstDataArray(array, list); 131 computeMaxPower(array, list, startNS, endNS, totalNS, frame); 132 } 133} 134 135function setFirstDataArray(array: EnergyPowerStruct[], list: Array<EnergyPowerStruct>): void { 136 array.forEach((item) => { 137 if ( 138 list.length > 0 && 139 item.ts + 500000000 >= list[list.length - 1].ts && 140 item.ts - 500000000 <= list[list.length - 1].ts 141 ) { 142 list[list.length - 1].cpu = item.cpu === 0 ? list[list.length - 1].cpu : item.cpu; 143 list[list.length - 1].location = item.location === 0 ? list[list.length - 1].location : item.location; 144 list[list.length - 1].gpu = item.gpu === 0 ? list[list.length - 1].gpu : item.gpu; 145 list[list.length - 1].display = item.display === 0 ? list[list.length - 1].display : item.display; 146 list[list.length - 1].camera = item.camera === 0 ? list[list.length - 1].camera : item.camera; 147 list[list.length - 1].bluetooth = item.bluetooth === 0 ? list[list.length - 1].bluetooth : item.bluetooth; 148 list[list.length - 1].flashlight = item.flashlight === 0 ? list[list.length - 1].flashlight : item.flashlight; 149 list[list.length - 1].audio = item.audio === 0 ? list[list.length - 1].audio : item.audio; 150 list[list.length - 1].wifiscan = item.wifiscan === 0 ? list[list.length - 1].wifiscan : item.wifiscan; 151 } else { 152 list.push(item); 153 } 154 }); 155} 156 157function computeMaxPower( 158 array: Array<EnergyPowerStruct>, 159 list: Array<EnergyPowerStruct>, 160 startNS: number, 161 endNS: number, 162 totalNS: number, 163 frame: Rect 164): void { 165 array.forEach((item) => { 166 if (list.indexOf(item) >= 0) { 167 EnergyPowerStruct.setPowerFrame(item, 5, startNS || 0, endNS || 0, totalNS || 0, frame); 168 let max = 169 (item.cpu || 0) + 170 (item.location || 0) + 171 (item.gpu || 0) + 172 (item.display || 0) + 173 (item.camera || 0) + 174 (item.bluetooth || 0) + 175 (item.flashlight || 0) + 176 (item.audio || 0) + 177 (item.wifiscan || 0); 178 if (max > EnergyPowerStruct.maxPower) { 179 EnergyPowerStruct.maxPower = max; 180 } 181 } 182 }); 183} 184 185export class EnergyPowerStruct extends BaseStruct { 186 static maxPower: number = 0; 187 static maxPowerName: string = '0'; 188 static powerItemNumber: number = 9; 189 static currentTextWidth: number = 0; 190 static rowHeight: number = 200; 191 static appName: string | undefined; 192 static hoverEnergyPowerStruct: EnergyPowerStruct | undefined; 193 static selectEnergyPowerStruct: EnergyPowerStruct | undefined; 194 static OFFSET_WIDTH: number = 266; 195 name: string | undefined; 196 appKey: string | undefined; 197 eventValue: string | undefined; 198 eventName: string | undefined; 199 id: number | undefined; 200 ts: number = 0; 201 cpu: number = 0; 202 location: number = 0; 203 gpu: number = 0; 204 display: number = 0; 205 camera: number = 0; 206 bluetooth: number = 0; 207 flashlight: number = 0; 208 audio: number = 0; 209 wifiscan: number = 0; 210 211 static draw( 212 req: { useCache: boolean; context: CanvasRenderingContext2D; type: string; appName: string }, 213 index: number, 214 data: EnergyPowerStruct, 215 row: TraceRow<EnergyPowerStruct> 216 ): void { 217 if (data.frame) { 218 req!.context.globalAlpha = 1.0; 219 req!.context.lineWidth = 1; 220 this.currentTextWidth = 0; 221 let cpuHeight = this.drawHistogram(req, data, -1, data.cpu!, 'CPU', row.frame); 222 let locationHeight = this.drawHistogram(req, data, cpuHeight, data.location!, 'LOCATION', row.frame); 223 let gpuHeight = this.drawHistogram(req, data, cpuHeight - locationHeight, data.gpu!, 'GPU', row.frame); 224 let dHight = cpuHeight - locationHeight - gpuHeight; 225 let displayHeight = this.drawHistogram(req, data, dHight, data.display!, 'DISPLAY', row.frame); 226 let cHight = cpuHeight - locationHeight - gpuHeight - displayHeight; 227 let cameraHeight = this.drawHistogram(req, data, cHight, data.camera!, 'CAMERA', row.frame); 228 let bHeight = cpuHeight - locationHeight - gpuHeight - displayHeight - cameraHeight; 229 let bluetoothHeight = this.drawHistogram(req, data, bHeight, data.bluetooth!, 'BLUETOOTH', row.frame); 230 let fHeight = cpuHeight - locationHeight - gpuHeight - displayHeight - cameraHeight - bluetoothHeight; 231 let flashlightHeight = this.drawHistogram(req, data, fHeight, data.flashlight!, 'FLASHLIGHT', row.frame); 232 let aHeight = 233 cpuHeight - locationHeight - gpuHeight - displayHeight - cameraHeight - bluetoothHeight - flashlightHeight; 234 let audioHeight = this.drawHistogram(req, data, aHeight, data.audio!, 'AUDIO', row.frame); 235 let wHeight = 236 cpuHeight - 237 locationHeight - 238 gpuHeight - 239 displayHeight - 240 cameraHeight - 241 bluetoothHeight - 242 flashlightHeight - 243 audioHeight; 244 let wifiHeight = this.drawHistogram(req, data, wHeight, data.wifiscan!, 'WIFISCAN', row.frame); 245 let maxPointY = this.drawPolyline(req, index, data, row.frame, wifiHeight); 246 let startNS = TraceRow.range!.startNS; 247 let endNS = TraceRow.range!.endNS; 248 let totalNS = TraceRow.range!.totalNS; 249 if (data.ts === EnergyPowerStruct.hoverEnergyPowerStruct?.ts) { 250 let endPointX = ns2x((data.ts || 0) + 500000000, startNS, endNS, totalNS, row.frame); 251 let startPointX = ns2x((data.ts || 0) - 500000000, startNS, endNS, totalNS, row.frame); 252 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 253 req.context.globalAlpha = 1; 254 req!.context.lineWidth = 2; 255 req.context.fillStyle = '#333'; 256 req!.context.strokeRect(startPointX, maxPointY, frameWidth, req.context.canvas.width - maxPointY); 257 } 258 } 259 req!.context.globalAlpha = 1.0; 260 req!.context.lineWidth = 1; 261 } 262 263 static drawHistogram( 264 req: { useCache: boolean; context: CanvasRenderingContext2D; type: string; appName: string }, 265 data: EnergyPowerStruct, 266 height: number, 267 itemValue: number, 268 textItem: string, 269 rowFrame: Rect 270 ): number { 271 let endPointX = ns2x( 272 (data.ts || 0) + 500000000, 273 TraceRow.range!.startNS, 274 TraceRow.range!.endNS, 275 TraceRow.range!.totalNS, 276 rowFrame 277 ); 278 let startPointX = ns2x( 279 (data.ts || 0) - 500000000, 280 TraceRow.range!.startNS, 281 TraceRow.range!.endNS, 282 TraceRow.range!.totalNS, 283 rowFrame 284 ); 285 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 286 let histogramColor = this.getHistogramColor(textItem); 287 req!.context.fillStyle = histogramColor; 288 req!.context.strokeStyle = histogramColor; 289 let dataHeight: number = Math.floor(((itemValue || 0) * (this.rowHeight - 40)) / EnergyPowerStruct.maxPower); 290 if (itemValue !== 0 && dataHeight < 15) { 291 dataHeight = 15; 292 } 293 let drawStartY = 0; 294 295 if (height === -1) { 296 drawStartY = data.frame!.y + this.rowHeight - dataHeight + 4; 297 req!.context.fillRect(startPointX, drawStartY, frameWidth, dataHeight); 298 return drawStartY; 299 } else { 300 drawStartY = height - dataHeight; 301 req!.context.fillRect(startPointX, drawStartY, frameWidth, dataHeight); 302 if (textItem === 'WIFISCAN') { 303 return drawStartY; 304 } 305 return dataHeight; 306 } 307 } 308 309 static drawPolyline( 310 req: { useCache: boolean; context: CanvasRenderingContext2D; type: string; appName: string }, 311 index: number, 312 data: EnergyPowerStruct, 313 rowFrame: Rect, 314 totalHeight: number 315 ): number { 316 let pointX = ns2x(data.ts || 0, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, rowFrame); 317 let maxHeight = 318 (data.cpu || 0) + 319 (data.location || 0) + 320 (data.gpu || 0) + 321 (data.display || 0) + 322 (data.camera || 0) + 323 (data.bluetooth || 0) + 324 (data.flashlight || 0) + 325 (data.audio || 0) + 326 (data.wifiscan || 0); 327 let drawHeight: number = Math.floor(((maxHeight || 0) * (this.rowHeight - 40)) / EnergyPowerStruct.maxPower); 328 let drawY = data.frame!.y + this.rowHeight - drawHeight + 5; 329 req!.context.fillStyle = '#ED6F21'; 330 req!.context.strokeStyle = '#ED6F21'; 331 332 if (index === 0) { 333 req.context.beginPath(); 334 req.context.arc(pointX, totalHeight, 4, 0, 2 * Math.PI); 335 req.context.fill(); 336 req.context.moveTo(pointX, totalHeight); 337 } else { 338 req.context.lineTo(pointX, totalHeight); 339 req.context.stroke(); 340 req.context.beginPath(); 341 req.context.arc(pointX, totalHeight, 4, 0, 2 * Math.PI); 342 req.context.fill(); 343 } 344 return totalHeight; 345 } 346 347 static setPowerFrame( 348 powerNode: unknown, 349 padding: number, 350 startNS: number, 351 endNS: number, 352 totalNS: number, 353 frame: unknown 354 ): void { 355 let startPointX: number; 356 let endPointX: number; 357 //@ts-ignore 358 if ((powerNode.ts || 0) < startNS) { 359 startPointX = 0; 360 } else { 361 //@ts-ignore 362 startPointX = ns2x((powerNode.ts || 0) - 500000000, startNS, endNS, totalNS, frame); 363 } 364 //@ts-ignore 365 if (powerNode.ts + 500000000 > endNS) { 366 //@ts-ignore 367 endPointX = frame.width; 368 } else { 369 //@ts-ignore 370 endPointX = ns2x(powerNode.ts + 500000000, startNS, endNS, totalNS, frame); 371 } 372 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 373 //@ts-ignore 374 if (!powerNode.frame) { 375 //@ts-ignore 376 powerNode.frame = {}; 377 } 378 //@ts-ignore 379 powerNode.frame.x = Math.floor(startPointX); 380 //@ts-ignore 381 powerNode.frame.y = frame.y + padding; 382 //@ts-ignore 383 powerNode.frame.width = Math.ceil(frameWidth); 384 //@ts-ignore 385 powerNode.frame.height = Math.floor(frame.height - padding * 2); 386 } 387 388 static getHistogramColor(textItem: string): string { 389 switch (textItem) { 390 case 'CPU': 391 return '#92D6CC'; 392 case 'LOCATION': 393 return '#61CFBE'; 394 case 'GPU': 395 return '#86C5E3'; 396 case 'DISPLAY': 397 return '#46B1E3'; 398 case 'CAMERA': 399 return '#C386F0'; 400 case 'BLUETOOTH': 401 return '#8981F7'; 402 case 'AUDIO': 403 return '#AC49F5'; 404 case 'WIFISCAN': 405 return '#92C4BD'; 406 default: 407 return '#564AF7'; 408 } 409 } 410} 411