Home | History | Annotate | Download | only in cpu_freq
      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