• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}:&nbsp;&nbsp;</div>
407        <div style="line-height: 15px; flex-grow: 1; flex-shrink: 1; flex-basis: auto;">${
408          energy || 0
409        } mAh&nbsp;&nbsp;</div>
410        <div style="line-height: 15px; flex-grow: 1; flex-shrink: 1; flex-basis: auto;">&nbsp;&nbsp;${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