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 {search} from '../../base/binary_search'; 16import {assertTrue} from '../../base/logging'; 17import {TrackState} from '../../common/state'; 18import {checkerboardExcept} from '../../frontend/checkerboard'; 19import {hueForCpu} from '../../frontend/colorizer'; 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) return; // Can't possibly draw anything. 60 61 assertTrue(data.tsStarts.length === data.freqKHz.length); 62 assertTrue(data.freqKHz.length === data.idles.length); 63 64 const startPx = Math.floor(timeScale.timeToPx(visibleWindowTime.start)); 65 const endPx = Math.floor(timeScale.timeToPx(visibleWindowTime.end)); 66 const zeroY = MARGIN_TOP + RECT_HEIGHT; 67 68 let lastX = startPx; 69 let lastY = zeroY; 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.frontendLocalState.hoveredUtid !== -1) { 86 saturation = 0; 87 } 88 ctx.fillStyle = `hsl(${hue}, ${saturation}%, 70%)`; 89 ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`; 90 ctx.beginPath(); 91 ctx.moveTo(lastX, lastY); 92 93 for (let i = 0; i < data.freqKHz.length; i++) { 94 const value = data.freqKHz[i]; 95 const startTime = data.tsStarts[i]; 96 const nextY = zeroY - Math.round((value / yMax) * RECT_HEIGHT); 97 if (nextY === lastY) continue; 98 99 lastX = Math.floor(timeScale.timeToPx(startTime)); 100 ctx.lineTo(lastX, lastY); 101 ctx.lineTo(lastX, nextY); 102 lastY = nextY; 103 } 104 // Find the end time for the last frequency event and then draw 105 // down to zero to show that we do not have data after that point. 106 const endTime = data.tsEnds[data.freqKHz.length - 1]; 107 const finalX = Math.floor(timeScale.timeToPx(endTime)); 108 ctx.lineTo(finalX, lastY); 109 ctx.lineTo(finalX, zeroY); 110 ctx.lineTo(endPx, zeroY); 111 ctx.closePath(); 112 ctx.fill(); 113 ctx.stroke(); 114 115 // Draw CPU idle rectangles that overlay the CPU freq graph. 116 ctx.fillStyle = `rgba(240, 240, 240, 1)`; 117 const bottomY = MARGIN_TOP + RECT_HEIGHT; 118 119 for (let i = 0; i < data.freqKHz.length; i++) { 120 if (data.idles[i] >= 0) { 121 const value = data.freqKHz[i]; 122 const firstX = Math.floor(timeScale.timeToPx(data.tsStarts[i])); 123 const secondX = Math.floor(timeScale.timeToPx(data.tsEnds[i])); 124 const lastY = zeroY - Math.round((value / yMax) * RECT_HEIGHT); 125 ctx.fillRect(firstX, bottomY, secondX - firstX, lastY - bottomY); 126 } 127 } 128 129 ctx.font = '10px Roboto Condensed'; 130 131 if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) { 132 let text = `${this.hoveredValue.toLocaleString()}kHz`; 133 if (data.isQuantized) { 134 text = `${this.hoveredValue.toLocaleString()}kHz (weighted avg)`; 135 } 136 137 ctx.fillStyle = `hsl(${hue}, 45%, 75%)`; 138 ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`; 139 140 const xStart = Math.floor(timeScale.timeToPx(this.hoveredTs)); 141 const xEnd = this.hoveredTsEnd === undefined ? 142 endPx : 143 Math.floor(timeScale.timeToPx(this.hoveredTsEnd)); 144 const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT); 145 146 // Highlight line. 147 ctx.beginPath(); 148 ctx.moveTo(xStart, y); 149 ctx.lineTo(xEnd, y); 150 ctx.lineWidth = 3; 151 ctx.stroke(); 152 ctx.lineWidth = 1; 153 154 // Draw change marker. 155 ctx.beginPath(); 156 ctx.arc(xStart, y, 3 /*r*/, 0 /*start angle*/, 2 * Math.PI /*end angle*/); 157 ctx.fill(); 158 ctx.stroke(); 159 160 // Display idle value if current hover is idle. 161 if (this.hoveredIdle !== undefined && this.hoveredIdle !== -1) { 162 // Display the idle value +1 to be consistent with catapult. 163 text += ` (Idle: ${(this.hoveredIdle + 1).toLocaleString()})`; 164 } 165 166 // Draw the tooltip. 167 this.drawTrackHoverTooltip(ctx, this.mouseXpos, text); 168 } 169 170 // Write the Y scale on the top left corner. 171 ctx.textBaseline = 'alphabetic'; 172 ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; 173 ctx.fillRect(0, 0, 42, 18); 174 ctx.fillStyle = '#666'; 175 ctx.textAlign = 'left'; 176 ctx.fillText(`${yLabel}`, 4, 14); 177 178 // If the cached trace slices don't fully cover the visible time range, 179 // show a gray rectangle with a "Loading..." label. 180 checkerboardExcept( 181 ctx, 182 this.getHeight(), 183 timeScale.timeToPx(visibleWindowTime.start), 184 timeScale.timeToPx(visibleWindowTime.end), 185 timeScale.timeToPx(data.start), 186 timeScale.timeToPx(data.end)); 187 } 188 189 onMouseMove({x}: {x: number, y: number}) { 190 const data = this.data(); 191 if (data === undefined) return; 192 this.mouseXpos = x; 193 const {timeScale} = globals.frontendLocalState; 194 const time = timeScale.pxToTime(x); 195 196 const index = search(data.tsStarts, time); 197 this.hoveredTs = index === -1 ? undefined : data.tsStarts[index]; 198 this.hoveredTsEnd = index === -1 ? undefined : data.tsEnds[index]; 199 this.hoveredValue = index === -1 ? undefined : data.freqKHz[index]; 200 this.hoveredIdle = index === -1 ? undefined : data.idles[index]; 201 } 202 203 onMouseOut() { 204 this.hoveredValue = undefined; 205 this.hoveredTs = undefined; 206 this.hoveredTsEnd = undefined; 207 this.hoveredIdle = undefined; 208 } 209} 210 211trackRegistry.register(CpuFreqTrack); 212