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 {TrackState} from '../../common/state'; 19import {checkerboardExcept} from '../../frontend/checkerboard'; 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 || data.timestamps.length === 0) { 60 // Can't possibly draw anything. 61 return; 62 } 63 64 assertTrue(data.timestamps.length === data.lastFreqKHz.length); 65 assertTrue(data.timestamps.length === data.minFreqKHz.length); 66 assertTrue(data.timestamps.length === data.maxFreqKHz.length); 67 assertTrue(data.timestamps.length === data.lastIdleValues.length); 68 69 const endPx = timeScale.timeToPx(visibleWindowTime.end); 70 const zeroY = MARGIN_TOP + RECT_HEIGHT; 71 72 // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K). 73 let yMax = data.maximumValue; 74 const kUnits = ['', 'K', 'M', 'G', 'T', 'E']; 75 const exp = Math.ceil(Math.log10(Math.max(yMax, 1))); 76 const pow10 = Math.pow(10, exp); 77 yMax = Math.ceil(yMax / (pow10 / 4)) * (pow10 / 4); 78 const unitGroup = Math.floor(exp / 3); 79 const num = yMax / Math.pow(10, unitGroup * 3); 80 // The values we have for cpufreq are in kHz so +1 to unitGroup. 81 const yLabel = `${num} ${kUnits[unitGroup + 1]}Hz`; 82 83 // Draw the CPU frequency graph. 84 const hue = hueForCpu(this.config.cpu); 85 let saturation = 45; 86 if (globals.frontendLocalState.hoveredUtid !== -1) { 87 saturation = 0; 88 } 89 ctx.fillStyle = `hsl(${hue}, ${saturation}%, 70%)`; 90 ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`; 91 92 const calculateX = (timestamp: number) => { 93 return Math.floor(timeScale.timeToPx(timestamp)); 94 }; 95 const calculateY = (value: number) => { 96 return zeroY - Math.round((value / yMax) * RECT_HEIGHT); 97 }; 98 99 const [rawStartIdx,] = 100 searchSegment(data.timestamps, visibleWindowTime.start); 101 const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx; 102 103 const [, rawEndIdx] = searchSegment(data.timestamps, visibleWindowTime.end); 104 const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx; 105 106 ctx.beginPath(); 107 ctx.moveTo(Math.max(calculateX(data.timestamps[startIdx]), 0), zeroY); 108 109 let lastDrawnY = zeroY; 110 for (let i = startIdx; i < endIdx; i++) { 111 const x = calculateX(data.timestamps[i]); 112 113 const minY = calculateY(data.minFreqKHz[i]); 114 const maxY = calculateY(data.maxFreqKHz[i]); 115 const lastY = calculateY(data.lastFreqKHz[i]); 116 117 ctx.lineTo(x, lastDrawnY); 118 if (minY === maxY) { 119 assertTrue(lastY === minY); 120 ctx.lineTo(x, lastY); 121 } else { 122 ctx.lineTo(x, minY); 123 ctx.lineTo(x, maxY); 124 ctx.lineTo(x, lastY); 125 } 126 lastDrawnY = lastY; 127 } 128 // Find the end time for the last frequency event and then draw 129 // down to zero to show that we do not have data after that point. 130 const finalX = Math.min(calculateX(data.maxTsEnd), endPx); 131 ctx.lineTo(finalX, lastDrawnY); 132 ctx.lineTo(finalX, zeroY); 133 ctx.lineTo(endPx, zeroY); 134 ctx.closePath(); 135 ctx.fill(); 136 ctx.stroke(); 137 138 // Draw CPU idle rectangles that overlay the CPU freq graph. 139 ctx.fillStyle = `rgba(240, 240, 240, 1)`; 140 141 for (let i = 0; i < data.lastIdleValues.length; i++) { 142 if (data.lastIdleValues[i] < 0) { 143 continue; 144 } 145 146 // We intentionally don't use the floor function here when computing x 147 // coordinates. Instead we use floating point which prevents flickering as 148 // we pan and zoom; this relies on the browser anti-aliasing pixels 149 // correctly. 150 const x = timeScale.timeToPx(data.timestamps[i]); 151 const xEnd = i === data.lastIdleValues.length - 1 ? 152 finalX : 153 timeScale.timeToPx(data.timestamps[i + 1]); 154 155 const width = xEnd - x; 156 const height = calculateY(data.lastFreqKHz[i]) - zeroY; 157 158 ctx.fillRect(x, zeroY, width, height); 159 } 160 161 ctx.font = '10px Roboto Condensed'; 162 163 if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) { 164 let text = `${this.hoveredValue.toLocaleString()}kHz`; 165 166 ctx.fillStyle = `hsl(${hue}, 45%, 75%)`; 167 ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`; 168 169 const xStart = Math.floor(timeScale.timeToPx(this.hoveredTs)); 170 const xEnd = this.hoveredTsEnd === undefined ? 171 endPx : 172 Math.floor(timeScale.timeToPx(this.hoveredTsEnd)); 173 const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT); 174 175 // Highlight line. 176 ctx.beginPath(); 177 ctx.moveTo(xStart, y); 178 ctx.lineTo(xEnd, y); 179 ctx.lineWidth = 3; 180 ctx.stroke(); 181 ctx.lineWidth = 1; 182 183 // Draw change marker. 184 ctx.beginPath(); 185 ctx.arc(xStart, y, 3 /*r*/, 0 /*start angle*/, 2 * Math.PI /*end angle*/); 186 ctx.fill(); 187 ctx.stroke(); 188 189 // Display idle value if current hover is idle. 190 if (this.hoveredIdle !== undefined && this.hoveredIdle !== -1) { 191 // Display the idle value +1 to be consistent with catapult. 192 text += ` (Idle: ${(this.hoveredIdle + 1).toLocaleString()})`; 193 } 194 195 // Draw the tooltip. 196 this.drawTrackHoverTooltip(ctx, this.mouseXpos, text); 197 } 198 199 // Write the Y scale on the top left corner. 200 ctx.textBaseline = 'alphabetic'; 201 ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; 202 ctx.fillRect(0, 0, 42, 18); 203 ctx.fillStyle = '#666'; 204 ctx.textAlign = 'left'; 205 ctx.fillText(`${yLabel}`, 4, 14); 206 207 // If the cached trace slices don't fully cover the visible time range, 208 // show a gray rectangle with a "Loading..." label. 209 checkerboardExcept( 210 ctx, 211 this.getHeight(), 212 timeScale.timeToPx(visibleWindowTime.start), 213 timeScale.timeToPx(visibleWindowTime.end), 214 timeScale.timeToPx(data.start), 215 timeScale.timeToPx(data.end)); 216 } 217 218 onMouseMove({x}: {x: number, y: number}) { 219 const data = this.data(); 220 if (data === undefined) return; 221 this.mouseXpos = x; 222 const {timeScale} = globals.frontendLocalState; 223 const time = timeScale.pxToTime(x); 224 225 const [left, right] = searchSegment(data.timestamps, time); 226 this.hoveredTs = left === -1 ? undefined : data.timestamps[left]; 227 this.hoveredTsEnd = right === -1 ? undefined : data.timestamps[right]; 228 this.hoveredValue = left === -1 ? undefined : data.lastFreqKHz[left]; 229 this.hoveredIdle = left === -1 ? undefined : data.lastIdleValues[left]; 230 } 231 232 onMouseOut() { 233 this.hoveredValue = undefined; 234 this.hoveredTs = undefined; 235 this.hoveredTsEnd = undefined; 236 this.hoveredIdle = undefined; 237 } 238} 239 240trackRegistry.register(CpuFreqTrack); 241