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