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