blob: c8b2d748422b6f5379b323dbd5d8b60a3a4410e3 [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {searchSegment} from '../../base/binary_search';
import {assertTrue} from '../../base/logging';
import {hueForCpu} from '../../common/colorizer';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
import {NewTrackArgs, Track} from '../../frontend/track';
import {trackRegistry} from '../../frontend/track_registry';
import {
Config,
CPU_FREQ_TRACK_KIND,
Data,
} from './common';
// 0.5 Makes the horizontal lines sharp.
const MARGIN_TOP = 4.5;
const RECT_HEIGHT = 20;
class CpuFreqTrack extends Track<Config, Data> {
static readonly kind = CPU_FREQ_TRACK_KIND;
static create(args: NewTrackArgs): CpuFreqTrack {
return new CpuFreqTrack(args);
}
private mousePos = {x: 0, y: 0};
private hoveredValue: number|undefined = undefined;
private hoveredTs: number|undefined = undefined;
private hoveredTsEnd: number|undefined = undefined;
private hoveredIdle: number|undefined = undefined;
constructor(args: NewTrackArgs) {
super(args);
}
getHeight() {
return MARGIN_TOP + RECT_HEIGHT;
}
renderCanvas(ctx: CanvasRenderingContext2D): void {
// TODO: fonts and colors should come from the CSS and not hardcoded here.
const {timeScale, visibleWindowTime} = globals.frontendLocalState;
const data = this.data();
if (data === undefined || data.timestamps.length === 0) {
// Can't possibly draw anything.
return;
}
assertTrue(data.timestamps.length === data.lastFreqKHz.length);
assertTrue(data.timestamps.length === data.minFreqKHz.length);
assertTrue(data.timestamps.length === data.maxFreqKHz.length);
assertTrue(data.timestamps.length === data.lastIdleValues.length);
const endPx = timeScale.timeToPx(visibleWindowTime.end);
const zeroY = MARGIN_TOP + RECT_HEIGHT;
// Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
let yMax = data.maximumValue;
const kUnits = ['', 'K', 'M', 'G', 'T', 'E'];
const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
const pow10 = Math.pow(10, exp);
yMax = Math.ceil(yMax / (pow10 / 4)) * (pow10 / 4);
const unitGroup = Math.floor(exp / 3);
const num = yMax / Math.pow(10, unitGroup * 3);
// The values we have for cpufreq are in kHz so +1 to unitGroup.
const yLabel = `${num} ${kUnits[unitGroup + 1]}Hz`;
// Draw the CPU frequency graph.
const hue = hueForCpu(this.config.cpu);
let saturation = 45;
if (globals.state.hoveredUtid !== -1) {
saturation = 0;
}
ctx.fillStyle = `hsl(${hue}, ${saturation}%, 70%)`;
ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`;
const calculateX = (timestamp: number) => {
return Math.floor(timeScale.timeToPx(timestamp));
};
const calculateY = (value: number) => {
return zeroY - Math.round((value / yMax) * RECT_HEIGHT);
};
const [rawStartIdx,] =
searchSegment(data.timestamps, visibleWindowTime.start);
const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
const [, rawEndIdx] = searchSegment(data.timestamps, visibleWindowTime.end);
const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx;
ctx.beginPath();
ctx.moveTo(Math.max(calculateX(data.timestamps[startIdx]), 0), zeroY);
let lastDrawnY = zeroY;
for (let i = startIdx; i < endIdx; i++) {
const x = calculateX(data.timestamps[i]);
const minY = calculateY(data.minFreqKHz[i]);
const maxY = calculateY(data.maxFreqKHz[i]);
const lastY = calculateY(data.lastFreqKHz[i]);
ctx.lineTo(x, lastDrawnY);
if (minY === maxY) {
assertTrue(lastY === minY);
ctx.lineTo(x, lastY);
} else {
ctx.lineTo(x, minY);
ctx.lineTo(x, maxY);
ctx.lineTo(x, lastY);
}
lastDrawnY = lastY;
}
// Find the end time for the last frequency event and then draw
// down to zero to show that we do not have data after that point.
const finalX = Math.min(calculateX(data.maxTsEnd), endPx);
ctx.lineTo(finalX, lastDrawnY);
ctx.lineTo(finalX, zeroY);
ctx.lineTo(endPx, zeroY);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Draw CPU idle rectangles that overlay the CPU freq graph.
ctx.fillStyle = `rgba(240, 240, 240, 1)`;
for (let i = 0; i < data.lastIdleValues.length; i++) {
if (data.lastIdleValues[i] < 0) {
continue;
}
// We intentionally don't use the floor function here when computing x
// coordinates. Instead we use floating point which prevents flickering as
// we pan and zoom; this relies on the browser anti-aliasing pixels
// correctly.
const x = timeScale.timeToPx(data.timestamps[i]);
const xEnd = i === data.lastIdleValues.length - 1 ?
finalX :
timeScale.timeToPx(data.timestamps[i + 1]);
const width = xEnd - x;
const height = calculateY(data.lastFreqKHz[i]) - zeroY;
ctx.fillRect(x, zeroY, width, height);
}
ctx.font = '10px Roboto Condensed';
if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
let text = `${this.hoveredValue.toLocaleString()}kHz`;
ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
const xStart = Math.floor(timeScale.timeToPx(this.hoveredTs));
const xEnd = this.hoveredTsEnd === undefined ?
endPx :
Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT);
// Highlight line.
ctx.beginPath();
ctx.moveTo(xStart, y);
ctx.lineTo(xEnd, y);
ctx.lineWidth = 3;
ctx.stroke();
ctx.lineWidth = 1;
// Draw change marker.
ctx.beginPath();
ctx.arc(xStart, y, 3 /*r*/, 0 /*start angle*/, 2 * Math.PI /*end angle*/);
ctx.fill();
ctx.stroke();
// Display idle value if current hover is idle.
if (this.hoveredIdle !== undefined && this.hoveredIdle !== -1) {
// Display the idle value +1 to be consistent with catapult.
text += ` (Idle: ${(this.hoveredIdle + 1).toLocaleString()})`;
}
// Draw the tooltip.
this.drawTrackHoverTooltip(ctx, this.mousePos, text);
}
// Write the Y scale on the top left corner.
ctx.textBaseline = 'alphabetic';
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
ctx.fillRect(0, 0, 42, 18);
ctx.fillStyle = '#666';
ctx.textAlign = 'left';
ctx.fillText(`${yLabel}`, 4, 14);
// If the cached trace slices don't fully cover the visible time range,
// show a gray rectangle with a "Loading..." label.
checkerboardExcept(
ctx,
this.getHeight(),
timeScale.timeToPx(visibleWindowTime.start),
timeScale.timeToPx(visibleWindowTime.end),
timeScale.timeToPx(data.start),
timeScale.timeToPx(data.end));
}
onMouseMove(pos: {x: number, y: number}) {
const data = this.data();
if (data === undefined) return;
this.mousePos = pos;
const {timeScale} = globals.frontendLocalState;
const time = timeScale.pxToTime(pos.x);
const [left, right] = searchSegment(data.timestamps, time);
this.hoveredTs = left === -1 ? undefined : data.timestamps[left];
this.hoveredTsEnd = right === -1 ? undefined : data.timestamps[right];
this.hoveredValue = left === -1 ? undefined : data.lastFreqKHz[left];
this.hoveredIdle = left === -1 ? undefined : data.lastIdleValues[left];
}
onMouseOut() {
this.hoveredValue = undefined;
this.hoveredTs = undefined;
this.hoveredTsEnd = undefined;
this.hoveredIdle = undefined;
}
}
trackRegistry.register(CpuFreqTrack);