• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2019 The Android Open Source Project
2//
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
15import {search} from '../../base/binary_search';
16import {assertTrue} from '../../base/logging';
17import {TrackState} from '../../common/state';
18import {checkerboardExcept} from '../../frontend/checkerboard';
19import {hueForCpu} from '../../frontend/colorizer';
20import {globals} from '../../frontend/globals';
21import {Track} from '../../frontend/track';
22import {trackRegistry} from '../../frontend/track_registry';
23
24import {
25  Config,
26  CPU_FREQ_TRACK_KIND,
27  Data,
28} from './common';
29
30// 0.5 Makes the horizontal lines sharp.
31const MARGIN_TOP = 4.5;
32const RECT_HEIGHT = 20;
33
34class CpuFreqTrack extends Track<Config, Data> {
35  static readonly kind = CPU_FREQ_TRACK_KIND;
36  static create(trackState: TrackState): CpuFreqTrack {
37    return new CpuFreqTrack(trackState);
38  }
39
40  private mouseXpos = 0;
41  private hoveredValue: number|undefined = undefined;
42  private hoveredTs: number|undefined = undefined;
43  private hoveredTsEnd: number|undefined = undefined;
44  private hoveredIdle: number|undefined = undefined;
45
46  constructor(trackState: TrackState) {
47    super(trackState);
48  }
49
50  getHeight() {
51    return MARGIN_TOP + RECT_HEIGHT;
52  }
53
54  renderCanvas(ctx: CanvasRenderingContext2D): void {
55    // TODO: fonts and colors should come from the CSS and not hardcoded here.
56    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
57    const data = this.data();
58
59    if (data === undefined) return;  // Can't possibly draw anything.
60
61    assertTrue(data.tsStarts.length === data.freqKHz.length);
62    assertTrue(data.freqKHz.length === data.idles.length);
63
64    const startPx = Math.floor(timeScale.timeToPx(visibleWindowTime.start));
65    const endPx = Math.floor(timeScale.timeToPx(visibleWindowTime.end));
66    const zeroY = MARGIN_TOP + RECT_HEIGHT;
67
68    let lastX = startPx;
69    let lastY = zeroY;
70
71    // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
72    let yMax = data.maximumValue;
73    const kUnits = ['', 'K', 'M', 'G', 'T', 'E'];
74    const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
75    const pow10 = Math.pow(10, exp);
76    yMax = Math.ceil(yMax / (pow10 / 4)) * (pow10 / 4);
77    const unitGroup = Math.floor(exp / 3);
78    const num = yMax / Math.pow(10, unitGroup * 3);
79    // The values we have for cpufreq are in kHz so +1 to unitGroup.
80    const yLabel = `${num} ${kUnits[unitGroup + 1]}Hz`;
81
82    // Draw the CPU frequency graph.
83    const hue = hueForCpu(this.config.cpu);
84    let saturation = 45;
85    if (globals.frontendLocalState.hoveredUtid !== -1) {
86      saturation = 0;
87    }
88    ctx.fillStyle = `hsl(${hue}, ${saturation}%, 70%)`;
89    ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`;
90    ctx.beginPath();
91    ctx.moveTo(lastX, lastY);
92
93    for (let i = 0; i < data.freqKHz.length; i++) {
94      const value = data.freqKHz[i];
95      const startTime = data.tsStarts[i];
96      const nextY = zeroY - Math.round((value / yMax) * RECT_HEIGHT);
97      if (nextY === lastY) continue;
98
99      lastX = Math.floor(timeScale.timeToPx(startTime));
100      ctx.lineTo(lastX, lastY);
101      ctx.lineTo(lastX, nextY);
102      lastY = nextY;
103    }
104    // Find the end time for the last frequency event and then draw
105    // down to zero to show that we do not have data after that point.
106    const endTime = data.tsEnds[data.freqKHz.length - 1];
107    const finalX = Math.floor(timeScale.timeToPx(endTime));
108    ctx.lineTo(finalX, lastY);
109    ctx.lineTo(finalX, zeroY);
110    ctx.lineTo(endPx, zeroY);
111    ctx.closePath();
112    ctx.fill();
113    ctx.stroke();
114
115    // Draw CPU idle rectangles that overlay the CPU freq graph.
116    ctx.fillStyle = `rgba(240, 240, 240, 1)`;
117    const bottomY = MARGIN_TOP + RECT_HEIGHT;
118
119    for (let i = 0; i < data.freqKHz.length; i++) {
120      if (data.idles[i] >= 0) {
121        const value = data.freqKHz[i];
122        const firstX = Math.floor(timeScale.timeToPx(data.tsStarts[i]));
123        const secondX = Math.floor(timeScale.timeToPx(data.tsEnds[i]));
124        const lastY = zeroY - Math.round((value / yMax) * RECT_HEIGHT);
125        ctx.fillRect(firstX, bottomY, secondX - firstX, lastY - bottomY);
126      }
127    }
128
129    ctx.font = '10px Roboto Condensed';
130
131    if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
132      let text = `${this.hoveredValue.toLocaleString()}kHz`;
133      if (data.isQuantized) {
134        text = `${this.hoveredValue.toLocaleString()}kHz (weighted avg)`;
135      }
136
137      ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
138      ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
139
140      const xStart = Math.floor(timeScale.timeToPx(this.hoveredTs));
141      const xEnd = this.hoveredTsEnd === undefined ?
142          endPx :
143          Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
144      const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT);
145
146      // Highlight line.
147      ctx.beginPath();
148      ctx.moveTo(xStart, y);
149      ctx.lineTo(xEnd, y);
150      ctx.lineWidth = 3;
151      ctx.stroke();
152      ctx.lineWidth = 1;
153
154      // Draw change marker.
155      ctx.beginPath();
156      ctx.arc(xStart, y, 3 /*r*/, 0 /*start angle*/, 2 * Math.PI /*end angle*/);
157      ctx.fill();
158      ctx.stroke();
159
160      // Display idle value if current hover is idle.
161      if (this.hoveredIdle !== undefined && this.hoveredIdle !== -1) {
162        // Display the idle value +1 to be consistent with catapult.
163        text += ` (Idle: ${(this.hoveredIdle + 1).toLocaleString()})`;
164      }
165
166      // Draw the tooltip.
167      this.drawTrackHoverTooltip(ctx, this.mouseXpos, text);
168    }
169
170    // Write the Y scale on the top left corner.
171    ctx.textBaseline = 'alphabetic';
172    ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
173    ctx.fillRect(0, 0, 42, 18);
174    ctx.fillStyle = '#666';
175    ctx.textAlign = 'left';
176    ctx.fillText(`${yLabel}`, 4, 14);
177
178    // If the cached trace slices don't fully cover the visible time range,
179    // show a gray rectangle with a "Loading..." label.
180    checkerboardExcept(
181        ctx,
182        this.getHeight(),
183        timeScale.timeToPx(visibleWindowTime.start),
184        timeScale.timeToPx(visibleWindowTime.end),
185        timeScale.timeToPx(data.start),
186        timeScale.timeToPx(data.end));
187  }
188
189  onMouseMove({x}: {x: number, y: number}) {
190    const data = this.data();
191    if (data === undefined) return;
192    this.mouseXpos = x;
193    const {timeScale} = globals.frontendLocalState;
194    const time = timeScale.pxToTime(x);
195
196    const index = search(data.tsStarts, time);
197    this.hoveredTs = index === -1 ? undefined : data.tsStarts[index];
198    this.hoveredTsEnd = index === -1 ? undefined : data.tsEnds[index];
199    this.hoveredValue = index === -1 ? undefined : data.freqKHz[index];
200    this.hoveredIdle = index === -1 ? undefined : data.idles[index];
201  }
202
203  onMouseOut() {
204    this.hoveredValue = undefined;
205    this.hoveredTs = undefined;
206    this.hoveredTsEnd = undefined;
207    this.hoveredIdle = undefined;
208  }
209}
210
211trackRegistry.register(CpuFreqTrack);
212