• 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 {searchSegment} from '../../base/binary_search';
16import {assertTrue} from '../../base/logging';
17import {hueForCpu} from '../../common/colorizer';
18import {checkerboardExcept} from '../../frontend/checkerboard';
19import {globals} from '../../frontend/globals';
20import {NewTrackArgs, Track} from '../../frontend/track';
21import {trackRegistry} from '../../frontend/track_registry';
22
23import {
24  Config,
25  CPU_FREQ_TRACK_KIND,
26  Data,
27} from './common';
28
29// 0.5 Makes the horizontal lines sharp.
30const MARGIN_TOP = 4.5;
31const RECT_HEIGHT = 20;
32
33class CpuFreqTrack extends Track<Config, Data> {
34  static readonly kind = CPU_FREQ_TRACK_KIND;
35  static create(args: NewTrackArgs): CpuFreqTrack {
36    return new CpuFreqTrack(args);
37  }
38
39  private mousePos = {x: 0, y: 0};
40  private hoveredValue: number|undefined = undefined;
41  private hoveredTs: number|undefined = undefined;
42  private hoveredTsEnd: number|undefined = undefined;
43  private hoveredIdle: number|undefined = undefined;
44
45  constructor(args: NewTrackArgs) {
46    super(args);
47  }
48
49  getHeight() {
50    return MARGIN_TOP + RECT_HEIGHT;
51  }
52
53  renderCanvas(ctx: CanvasRenderingContext2D): void {
54    // TODO: fonts and colors should come from the CSS and not hardcoded here.
55    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
56    const data = this.data();
57
58    if (data === undefined || data.timestamps.length === 0) {
59      // Can't possibly draw anything.
60      return;
61    }
62
63    assertTrue(data.timestamps.length === data.lastFreqKHz.length);
64    assertTrue(data.timestamps.length === data.minFreqKHz.length);
65    assertTrue(data.timestamps.length === data.maxFreqKHz.length);
66    assertTrue(data.timestamps.length === data.lastIdleValues.length);
67
68    const endPx = timeScale.timeToPx(visibleWindowTime.end);
69    const zeroY = MARGIN_TOP + RECT_HEIGHT;
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.state.hoveredUtid !== -1) {
86      saturation = 0;
87    }
88    ctx.fillStyle = `hsl(${hue}, ${saturation}%, 70%)`;
89    ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`;
90
91    const calculateX = (timestamp: number) => {
92      return Math.floor(timeScale.timeToPx(timestamp));
93    };
94    const calculateY = (value: number) => {
95      return zeroY - Math.round((value / yMax) * RECT_HEIGHT);
96    };
97
98    const [rawStartIdx,] =
99      searchSegment(data.timestamps, visibleWindowTime.start);
100    const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
101
102    const [, rawEndIdx] = searchSegment(data.timestamps, visibleWindowTime.end);
103    const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx;
104
105    ctx.beginPath();
106    ctx.moveTo(Math.max(calculateX(data.timestamps[startIdx]), 0), zeroY);
107
108    let lastDrawnY = zeroY;
109    for (let i = startIdx; i < endIdx; i++) {
110      const x = calculateX(data.timestamps[i]);
111
112      const minY = calculateY(data.minFreqKHz[i]);
113      const maxY = calculateY(data.maxFreqKHz[i]);
114      const lastY = calculateY(data.lastFreqKHz[i]);
115
116      ctx.lineTo(x, lastDrawnY);
117      if (minY === maxY) {
118        assertTrue(lastY === minY);
119        ctx.lineTo(x, lastY);
120      } else {
121        ctx.lineTo(x, minY);
122        ctx.lineTo(x, maxY);
123        ctx.lineTo(x, lastY);
124      }
125      lastDrawnY = lastY;
126    }
127    // Find the end time for the last frequency event and then draw
128    // down to zero to show that we do not have data after that point.
129    const finalX = Math.min(calculateX(data.maxTsEnd), endPx);
130    ctx.lineTo(finalX, lastDrawnY);
131    ctx.lineTo(finalX, zeroY);
132    ctx.lineTo(endPx, zeroY);
133    ctx.closePath();
134    ctx.fill();
135    ctx.stroke();
136
137    // Draw CPU idle rectangles that overlay the CPU freq graph.
138    ctx.fillStyle = `rgba(240, 240, 240, 1)`;
139
140    for (let i = 0; i < data.lastIdleValues.length; i++) {
141      if (data.lastIdleValues[i] < 0) {
142        continue;
143      }
144
145      // We intentionally don't use the floor function here when computing x
146      // coordinates. Instead we use floating point which prevents flickering as
147      // we pan and zoom; this relies on the browser anti-aliasing pixels
148      // correctly.
149      const x = timeScale.timeToPx(data.timestamps[i]);
150      const xEnd = i === data.lastIdleValues.length - 1 ?
151          finalX :
152          timeScale.timeToPx(data.timestamps[i + 1]);
153
154      const width = xEnd - x;
155      const height = calculateY(data.lastFreqKHz[i]) - zeroY;
156
157      ctx.fillRect(x, zeroY, width, height);
158    }
159
160    ctx.font = '10px Roboto Condensed';
161
162    if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
163      let text = `${this.hoveredValue.toLocaleString()}kHz`;
164
165      ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
166      ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
167
168      const xStart = Math.floor(timeScale.timeToPx(this.hoveredTs));
169      const xEnd = this.hoveredTsEnd === undefined ?
170          endPx :
171          Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
172      const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT);
173
174      // Highlight line.
175      ctx.beginPath();
176      ctx.moveTo(xStart, y);
177      ctx.lineTo(xEnd, y);
178      ctx.lineWidth = 3;
179      ctx.stroke();
180      ctx.lineWidth = 1;
181
182      // Draw change marker.
183      ctx.beginPath();
184      ctx.arc(xStart, y, 3 /*r*/, 0 /*start angle*/, 2 * Math.PI /*end angle*/);
185      ctx.fill();
186      ctx.stroke();
187
188      // Display idle value if current hover is idle.
189      if (this.hoveredIdle !== undefined && this.hoveredIdle !== -1) {
190        // Display the idle value +1 to be consistent with catapult.
191        text += ` (Idle: ${(this.hoveredIdle + 1).toLocaleString()})`;
192      }
193
194      // Draw the tooltip.
195      this.drawTrackHoverTooltip(ctx, this.mousePos, text);
196    }
197
198    // Write the Y scale on the top left corner.
199    ctx.textBaseline = 'alphabetic';
200    ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
201    ctx.fillRect(0, 0, 42, 18);
202    ctx.fillStyle = '#666';
203    ctx.textAlign = 'left';
204    ctx.fillText(`${yLabel}`, 4, 14);
205
206    // If the cached trace slices don't fully cover the visible time range,
207    // show a gray rectangle with a "Loading..." label.
208    checkerboardExcept(
209        ctx,
210        this.getHeight(),
211        timeScale.timeToPx(visibleWindowTime.start),
212        timeScale.timeToPx(visibleWindowTime.end),
213        timeScale.timeToPx(data.start),
214        timeScale.timeToPx(data.end));
215  }
216
217  onMouseMove(pos: {x: number, y: number}) {
218    const data = this.data();
219    if (data === undefined) return;
220    this.mousePos = pos;
221    const {timeScale} = globals.frontendLocalState;
222    const time = timeScale.pxToTime(pos.x);
223
224    const [left, right] = searchSegment(data.timestamps, time);
225    this.hoveredTs = left === -1 ? undefined : data.timestamps[left];
226    this.hoveredTsEnd = right === -1 ? undefined : data.timestamps[right];
227    this.hoveredValue = left === -1 ? undefined : data.lastFreqKHz[left];
228    this.hoveredIdle = left === -1 ? undefined : data.lastIdleValues[left];
229  }
230
231  onMouseOut() {
232    this.hoveredValue = undefined;
233    this.hoveredTs = undefined;
234    this.hoveredTsEnd = undefined;
235    this.hoveredIdle = undefined;
236  }
237}
238
239trackRegistry.register(CpuFreqTrack);
240