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