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, Rect, Render, ns2x } from './ProcedureWorkerCommon'; 17import { TraceRow } from '../../component/trace/base/TraceRow'; 18import { ColorUtils } from '../../component/trace/base/ColorUtils'; 19import { SpSystemTrace } from '../../component/SpSystemTrace'; 20 21enum Type { 22 'AUDIO', 23 'BLUETOOTH', 24 'CAMERA', 25 'CPU', 26 'DISPLAY', 27 'FLASHLIGHT', 28 'GPU', 29 'LOCATION', 30 'WIFISCAN', 31 'WIFI', 32 'MODEM', 33} 34 35export class XpowerStatisticRender extends Render { 36 renderMainThread( 37 xpowerStasticReq: { 38 context: CanvasRenderingContext2D; 39 useCache: boolean; 40 }, 41 row: TraceRow<XpowerStatisticStruct> 42 ): void { 43 // offsetW控制图例的横向偏移量 确保图例不超过画布边界 因收藏和非收藏时泳道的宽度不一致 offsetW根据情况调整 44 let offsetW: number = row.collect ? 40 : 266; 45 let checkedType = row.rowSettingCheckedBoxList; 46 let checkedValue = row.rowSettingCheckBoxList; 47 let xpowerStatisticList = row.dataListCache.filter( 48 // @ts-ignore 49 (item) => checkedType[checkedValue?.indexOf(item.typeStr)] 50 ); 51 let xpowerStasticMap = new Map<number, XpowerStatisticStruct[]>(); 52 setGroupByTime(xpowerStasticMap, xpowerStatisticList); 53 let filter = Array.from({ length: xpowerStasticMap.size }, () => new XpowerStatisticStruct()); 54 setDataFilter(filter, xpowerStasticMap); 55 setDataFrameAndHoverHtml(filter, row, checkedValue!); 56 drawLoadingFrame(xpowerStasticReq.context, filter, row); 57 setMaxEnergyInfo(xpowerStasticReq.context, filter); 58 xpowerStasticReq.context.beginPath(); 59 let find = false; 60 for (let re of filter) { 61 XpowerStatisticStruct.draw(xpowerStasticReq, re, row, isFrameContainPoint(re.frame!, row.hoverX, row.hoverY)); 62 if (row.isHover && re.frame && isFrameContainPoint(re.frame, row.hoverX, row.hoverY)) { 63 XpowerStatisticStruct.hoverXpowerStruct = re; 64 find = true; 65 } 66 } 67 if (!find) { 68 XpowerStatisticStruct.hoverXpowerStruct = undefined; 69 } 70 xpowerStasticReq.context.closePath(); 71 let spApplication = document.getElementsByTagName('sp-application')[0]; 72 let isDark = spApplication && spApplication.hasAttribute('dark'); 73 drawLegend(xpowerStasticReq, checkedType!, checkedValue!, offsetW, isDark); 74 } 75} 76 77function setGroupByTime( 78 xpowerStasticMap: Map<number, XpowerStatisticStruct[]>, 79 xpowerStatisticList: XpowerStatisticStruct[] 80): void { 81 xpowerStatisticList.forEach((item) => { 82 if (xpowerStasticMap.has(item.startTime)) { 83 let data = xpowerStasticMap.get(item.startTime); 84 data!.push(item); 85 xpowerStasticMap.set(item.startTime, data!); 86 } else { 87 xpowerStasticMap.set(item.startTime, []); 88 let data = xpowerStasticMap.get(item.startTime); 89 data!.push(item); 90 xpowerStasticMap.set(item.startTime, data!); 91 } 92 }); 93} 94 95function setDataFilter( 96 filter: XpowerStatisticStruct[], 97 xpowerStasticMap: Map<number, XpowerStatisticStruct[]> 98): XpowerStatisticStruct[] { 99 let index = 0; 100 xpowerStasticMap.forEach((value, key) => { 101 const entry = filter[index]; 102 entry.startTime = key; 103 entry.totalEnergy = 0; 104 value.forEach((it) => updateEntry(entry, it)); 105 index += 1; 106 }); 107 return filter; 108} 109 110function updateEntry(entry: XpowerStatisticStruct, it: XpowerStatisticStruct): void { 111 switch (it.typeStr) { 112 case 'audio': 113 entry.audio = it.energy; 114 entry.audioDur = it.dur; 115 break; 116 case 'bluetooth': 117 entry.bluetooth = it.energy; 118 entry.bluetoothDur = it.dur; 119 break; 120 case 'camera': 121 entry.camera = it.energy; 122 entry.cameraDur = it.dur; 123 break; 124 case 'cpu': 125 entry.cpu = it.energy; 126 entry.cpuDur = it.dur; 127 break; 128 case 'display': 129 entry.display = it.energy; 130 entry.displayDur = it.dur; 131 break; 132 case 'flashlight': 133 entry.flashlight = it.energy; 134 entry.flashlightDur = it.dur; 135 break; 136 case 'gpu': 137 entry.gpu = it.energy; 138 entry.gpuDur = it.dur; 139 break; 140 case 'location': 141 entry.location = it.energy; 142 entry.locationDur = it.dur; 143 break; 144 case 'wifiscan': 145 entry.wifiscan = it.energy; 146 entry.wifiscanDur = it.dur; 147 break; 148 case 'wifi': 149 entry.wifi = it.energy; 150 entry.wifiDur = it.dur; 151 break; 152 case 'modem': 153 entry.modem = it.energy; 154 entry.modemDur = it.dur; 155 break; 156 } 157 entry.totalEnergy += it.energy; 158} 159 160function setDataFrameAndHoverHtml( 161 filter: XpowerStatisticStruct[], 162 row: TraceRow<XpowerStatisticStruct>, 163 text: string[] 164): void { 165 filter.forEach((item) => { 166 XpowerStatisticStruct.setXPowerStatisticFrame( 167 item, 168 5, 169 TraceRow.range?.startNS ?? 0, 170 TraceRow.range?.endNS ?? 0, 171 TraceRow.range?.totalNS ?? 0, 172 row.frame 173 ); 174 if (item.hoverHtml === '') { 175 XpowerStatisticStruct.setHoverHtml(item, text); 176 } 177 }); 178} 179 180function setMaxEnergyInfo(context: CanvasRenderingContext2D, filter: XpowerStatisticStruct[]): void { 181 XpowerStatisticStruct.computeMaxEnergy(filter); 182 let s = XpowerStatisticStruct.maxEnergy + ' mAh'; 183 let textMetrics = context.measureText(s); 184 context.globalAlpha = 0.8; 185 context.fillStyle = '#f0f0f0'; 186 context.fillRect(0, 5, textMetrics.width + 8, 18); 187 context.globalAlpha = 1; 188 context.fillStyle = '#333'; 189 context.textBaseline = 'middle'; 190 context.fillText(s, 4, 5 + 9); 191} 192 193export function drawLegend( 194 req: { context: CanvasRenderingContext2D; useCache: boolean }, 195 checked: boolean[], 196 checkedValue: string[], 197 offsetW: number, 198 isDark?: boolean 199): void { 200 let textList: string[] = []; 201 checkedValue.forEach((item, index) => { 202 if (checked[index]) { 203 textList.push(item.toUpperCase()); 204 } 205 }); 206 for (let index = 0; index < textList.length; index++) { 207 let text = req.context.measureText(textList[index]); 208 req.context.fillStyle = ColorUtils.colorForTid(checkedValue.indexOf(textList[index].toLowerCase())); 209 req.context.globalAlpha = 1; 210 let canvasEndX = req.context.canvas.clientWidth - offsetW; 211 let textColor = isDark ? '#FFFFFF' : '#333'; 212 if (index === 0) { 213 req!.context.fillRect(canvasEndX - textList.length * 80, 12, 8, 8); 214 req.context.globalAlpha = 0.8; 215 req.context.fillStyle = textColor; 216 req.context.textBaseline = 'middle'; 217 req.context.fillText(textList[index], canvasEndX - textList.length * 80 + 10, 18); 218 XpowerStatisticStruct.currentTextWidth = canvasEndX - textList.length * 80 + 40 + text.width; 219 } else { 220 req!.context.fillRect(XpowerStatisticStruct.currentTextWidth, 12, 8, 8); 221 req.context.globalAlpha = 0.8; 222 req.context.fillStyle = textColor; 223 req.context.textBaseline = 'middle'; 224 req!.context.fillText(textList[index], XpowerStatisticStruct.currentTextWidth + 12, 18); 225 XpowerStatisticStruct.currentTextWidth = XpowerStatisticStruct.currentTextWidth + 40 + text.width; 226 } 227 } 228 req.context.fillStyle = '#333'; 229} 230 231export function XpowerStatisticStructOnClick( 232 clickRowType: string, 233 sp: SpSystemTrace, 234 row: TraceRow<XpowerStatisticStruct>, 235 entry?: XpowerStatisticStruct 236): Promise<unknown> { 237 return new Promise((resolve, reject) => { 238 if (clickRowType === TraceRow.ROW_TYPE_XPOWER_STATISTIC && (XpowerStatisticStruct.hoverXpowerStruct || entry)) { 239 XpowerStatisticStruct.selectXpowerStruct = entry || XpowerStatisticStruct.hoverXpowerStruct; 240 sp.traceSheetEL?.displayXpowerStatisticData(XpowerStatisticStruct.selectXpowerStruct!); 241 sp.timerShaftEL?.modifyFlagList(undefined); 242 reject(new Error()); 243 } else { 244 resolve(null); 245 } 246 }); 247} 248 249export class XpowerStatisticStruct extends BaseStruct { 250 static rowHeight: number = 200; 251 static maxEnergy: number = 0; 252 static currentTextWidth: number = 0; 253 type: number = 0; 254 typeStr: string = ''; 255 startTime: number = 0; 256 dur: number = 0; 257 energy: number = 0; 258 static hoverXpowerStruct: XpowerStatisticStruct | undefined; 259 static selectXpowerStruct: XpowerStatisticStruct | undefined; 260 261 audio: number = 0; 262 bluetooth: number = 0; 263 camera: number = 0; 264 cpu: number = 0; 265 display: number = 0; 266 flashlight: number = 0; 267 gpu: number = 0; 268 location: number = 0; 269 wifiscan: number = 0; 270 wifi: number = 0; 271 modem: number = 0; 272 273 audioDur: number = 0; 274 bluetoothDur: number = 0; 275 cameraDur: number = 0; 276 cpuDur: number = 0; 277 displayDur: number = 0; 278 flashlightDur: number = 0; 279 gpuDur: number = 0; 280 locationDur: number = 0; 281 wifiscanDur: number = 0; 282 wifiDur: number = 0; 283 modemDur: number = 0; 284 285 totalEnergy: number = 0; 286 hoverHtml: string = ''; 287 288 static draw( 289 req: { context: CanvasRenderingContext2D; useCache: boolean }, 290 data: XpowerStatisticStruct, 291 row: TraceRow<XpowerStatisticStruct>, 292 isHover: boolean 293 ): void { 294 if (data.frame) { 295 req!.context.globalAlpha = 0.8; 296 req!.context.lineWidth = 1; 297 this.currentTextWidth = 0; 298 let cpuHeight = this.drawHistogram(req, data, -1, data.cpu!, Type.CPU, row.frame); 299 let locationHeight = this.drawHistogram(req, data, cpuHeight, data.location!, Type.LOCATION, row.frame); 300 let gpuHeight = this.drawHistogram(req, data, cpuHeight - locationHeight, data.gpu!, Type.GPU, row.frame); 301 let dHight = cpuHeight - locationHeight - gpuHeight; 302 let displayHeight = this.drawHistogram(req, data, dHight, data.display!, Type.DISPLAY, row.frame); 303 let cHight = dHight - displayHeight; 304 let cameraHeight = this.drawHistogram(req, data, cHight, data.camera!, Type.CAMERA, row.frame); 305 let bHeight = cHight - cameraHeight; 306 let bluetoothHeight = this.drawHistogram(req, data, bHeight, data.bluetooth!, Type.BLUETOOTH, row.frame); 307 let fHeight = bHeight - bluetoothHeight; 308 let flashlightHeight = this.drawHistogram(req, data, fHeight, data.flashlight!, Type.FLASHLIGHT, row.frame); 309 let aHeight = fHeight - flashlightHeight; 310 let audioHeight = this.drawHistogram(req, data, aHeight, data.audio!, Type.AUDIO, row.frame); 311 let wsHeight = aHeight - audioHeight; 312 let wifiScanHeight = this.drawHistogram(req, data, wsHeight, data.wifiscan!, Type.WIFISCAN, row.frame); 313 let mHeight = wsHeight - wifiScanHeight; 314 let modemHeight = this.drawHistogram(req, data, mHeight, data.modem!, Type.MODEM, row.frame); 315 let wHeight = mHeight - modemHeight; 316 let wifiHeight = this.drawHistogram(req, data, wHeight, data.wifi!, Type.WIFI, row.frame); 317 this.drawHoverFrame(data, isHover, wifiHeight, req, row); 318 } 319 req!.context.globalAlpha = 0.8; 320 req!.context.lineWidth = 1; 321 } 322 323 static drawHoverFrame( 324 data: XpowerStatisticStruct, 325 isHover: boolean, 326 wifiHeight: number, 327 req: { context: CanvasRenderingContext2D; useCache: boolean }, 328 row: TraceRow<XpowerStatisticStruct> 329 ): void { 330 let startNS = TraceRow.range!.startNS; 331 let endNS = TraceRow.range!.endNS; 332 let totalNS = TraceRow.range!.totalNS; 333 if ( 334 (data.startTime === XpowerStatisticStruct.hoverXpowerStruct?.startTime && isHover) || 335 (XpowerStatisticStruct.selectXpowerStruct && 336 data.startTime === XpowerStatisticStruct.selectXpowerStruct?.startTime) 337 ) { 338 let endPointX = ns2x((data.startTime || 0) + 3000000000, startNS, endNS, totalNS, row.frame); 339 let startPointX = ns2x(data.startTime || 0, startNS, endNS, totalNS, row.frame); 340 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 341 req.context.globalAlpha = 1; 342 req!.context.lineWidth = 2; 343 req.context.strokeStyle = '#9899a0'; 344 req!.context.strokeRect(startPointX, wifiHeight, frameWidth, data.frame!.height); 345 } 346 } 347 348 static drawHistogram( 349 req: { context: CanvasRenderingContext2D; useCache: boolean }, 350 data: XpowerStatisticStruct, 351 height: number, 352 itemValue: number, 353 type: number, 354 rowFrame: Rect 355 ): number { 356 let endPointX = ns2x( 357 data.startTime + 3000000000 || 0, 358 TraceRow.range!.startNS, 359 TraceRow.range!.endNS, 360 TraceRow.range!.totalNS, 361 rowFrame 362 ); 363 let startPointX = ns2x( 364 data.startTime || 0, 365 TraceRow.range!.startNS, 366 TraceRow.range!.endNS, 367 TraceRow.range!.totalNS, 368 rowFrame 369 ); 370 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 371 let histogramColor = ColorUtils.colorForTid(type); 372 req!.context.fillStyle = histogramColor; 373 let dataHeight: number = Math.floor(((itemValue || 0) * (this.rowHeight - 40)) / XpowerStatisticStruct.maxEnergy); 374 if (itemValue !== 0 && dataHeight < 15) { 375 dataHeight = 15; 376 } 377 let drawStartY = 0; 378 if (height === -1) { 379 drawStartY = data.frame!.y + this.rowHeight - dataHeight + 4; 380 req!.context.fillRect(startPointX, drawStartY, frameWidth, dataHeight); 381 data.frame!.y = drawStartY; 382 data.frame!.height += dataHeight; 383 return drawStartY; 384 } else { 385 drawStartY = height - dataHeight; 386 req!.context.fillRect(startPointX, drawStartY, frameWidth, dataHeight); 387 if (type === Type.WIFI) { 388 data.frame!.y = drawStartY; 389 data.frame!.height += dataHeight; 390 return drawStartY; 391 } 392 data.frame!.y = drawStartY; 393 data.frame!.height += dataHeight; 394 return dataHeight; 395 } 396 } 397 static setHoverHtml(node: XpowerStatisticStruct, text: string[]): void { 398 let hoverHtml = ''; 399 for (let key of text) { 400 // @ts-ignore 401 let energy = node[key]; 402 // @ts-ignore 403 let dur = node[key + 'Dur']; 404 if (energy !== 0) { 405 hoverHtml += `<div style=" display: flex; flex-wrap: nowrap; justify-content: space-between;"> 406 <div style="line-height: 15px; flex-grow: 2; flex-shrink: 1; flex-basis: auto;">${key}: </div> 407 <div style="line-height: 15px; flex-grow: 1; flex-shrink: 1; flex-basis: auto;">${ 408 energy || 0 409 } mAh </div> 410 <div style="line-height: 15px; flex-grow: 1; flex-shrink: 1; flex-basis: auto;"> ${dur + ' ms'}</div> 411 </div>`; 412 } 413 } 414 node.hoverHtml = hoverHtml; 415 } 416 417 static computeMaxEnergy(dataList: XpowerStatisticStruct[]): void { 418 let maxEnergy = 0; 419 dataList.forEach((item) => { 420 if (maxEnergy < item.totalEnergy) { 421 maxEnergy = item.totalEnergy; 422 } 423 }); 424 XpowerStatisticStruct.maxEnergy = maxEnergy; 425 } 426 427 static setXPowerStatisticFrame( 428 node: XpowerStatisticStruct, 429 padding: number, 430 startNS: number, 431 endNS: number, 432 totalNS: number, 433 frame: Rect 434 ): void { 435 let startPointX: number; 436 let endPointX: number; 437 if ((node.startTime || 0) < startNS) { 438 startPointX = 0; 439 } else { 440 startPointX = ns2x(node.startTime, startNS, endNS, totalNS, frame); 441 } 442 if (node.startTime + 3000000000 > endNS) { 443 endPointX = frame.width; 444 } else { 445 endPointX = ns2x(node.startTime + 3000000000, startNS, endNS, totalNS, frame); 446 } 447 let frameWidth = endPointX - startPointX <= 1 ? 1 : endPointX - startPointX; 448 if (!node.frame) { 449 node.frame = new Rect(0, 0, 0, 0); 450 } 451 node.frame.x = Math.floor(startPointX); 452 node.frame.y = frame.y + padding; 453 node.frame.width = Math.ceil(frameWidth); 454 node.frame.height = Math.floor(frame.height - padding * 2); 455 } 456} 457