Home | History | Annotate | Download | only in chrome_slices
      1 // Copyright (C) 2018 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 {cropText} from '../../common/canvas_utils';
     16 import {TrackState} from '../../common/state';
     17 import {checkerboardExcept} from '../../frontend/checkerboard';
     18 import {globals} from '../../frontend/globals';
     19 import {Track} from '../../frontend/track';
     20 import {trackRegistry} from '../../frontend/track_registry';
     21 
     22 import {Config, Data, SLICE_TRACK_KIND} from './common';
     23 
     24 const SLICE_HEIGHT = 20;
     25 const TRACK_PADDING = 5;
     26 
     27 function hash(s: string): number {
     28   let hash = 0x811c9dc5 & 0xfffffff;
     29   for (let i = 0; i < s.length; i++) {
     30     hash ^= s.charCodeAt(i);
     31     hash = (hash * 16777619) & 0xffffffff;
     32   }
     33   return hash & 0xff;
     34 }
     35 
     36 class ChromeSliceTrack extends Track<Config, Data> {
     37   static readonly kind = SLICE_TRACK_KIND;
     38   static create(trackState: TrackState): ChromeSliceTrack {
     39     return new ChromeSliceTrack(trackState);
     40   }
     41 
     42   private hoveredTitleId = -1;
     43 
     44   constructor(trackState: TrackState) {
     45     super(trackState);
     46   }
     47 
     48   renderCanvas(ctx: CanvasRenderingContext2D): void {
     49     // TODO: fonts and colors should come from the CSS and not hardcoded here.
     50 
     51     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     52     const data = this.data();
     53 
     54     // If there aren't enough cached slices data in |data| request more to
     55     // the controller.
     56     const inRange = data !== undefined &&
     57         (visibleWindowTime.start >= data.start &&
     58          visibleWindowTime.end <= data.end);
     59     if (!inRange || data === undefined ||
     60         data.resolution > globals.getCurResolution()) {
     61       globals.requestTrackData(this.trackState.id);
     62       if (data === undefined) return;  // Can't possibly draw anything.
     63     }
     64 
     65     // If the cached trace slices don't fully cover the visible time range,
     66     // show a gray rectangle with a "Loading..." label.
     67     checkerboardExcept(
     68         ctx,
     69         timeScale.timeToPx(visibleWindowTime.start),
     70         timeScale.timeToPx(visibleWindowTime.end),
     71         timeScale.timeToPx(data.start),
     72         timeScale.timeToPx(data.end), );
     73 
     74     ctx.font = '12px Google Sans';
     75     ctx.textAlign = 'center';
     76 
     77     // measuretext is expensive so we only use it once.
     78     const charWidth = ctx.measureText('ACBDLqsdfg').width / 10;
     79     const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
     80 
     81     for (let i = 0; i < data.starts.length; i++) {
     82       const tStart = data.starts[i];
     83       const tEnd = data.ends[i];
     84       const depth = data.depths[i];
     85       const cat = data.strings[data.categories[i]];
     86       const titleId = data.titles[i];
     87       const title = data.strings[titleId];
     88       if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
     89         continue;
     90       }
     91       const rectXStart = Math.max(timeScale.timeToPx(tStart), 0);
     92       const rectXEnd = Math.min(timeScale.timeToPx(tEnd), pxEnd);
     93       const rectWidth = rectXEnd - rectXStart;
     94       const rectYStart = TRACK_PADDING + depth * SLICE_HEIGHT;
     95 
     96       const hovered = titleId === this.hoveredTitleId;
     97       const hue = hash(cat);
     98       const saturation = Math.min(20 + depth * 10, 70);
     99       ctx.fillStyle = `hsl(${hue}, ${saturation}%, ${hovered ? 30 : 65}%)`;
    100       ctx.fillRect(rectXStart, rectYStart, rectWidth, SLICE_HEIGHT);
    101 
    102       ctx.fillStyle = 'white';
    103       const displayText = cropText(title, charWidth, rectWidth);
    104       const rectXCenter = rectXStart + rectWidth / 2;
    105       ctx.textBaseline = "middle";
    106       ctx.fillText(displayText, rectXCenter, rectYStart + SLICE_HEIGHT / 2);
    107     }
    108   }
    109 
    110   onMouseMove({x, y}: {x: number, y: number}) {
    111     const data = this.data();
    112     this.hoveredTitleId = -1;
    113     if (data === undefined) return;
    114     const {timeScale} = globals.frontendLocalState;
    115     if (y < TRACK_PADDING) return;
    116     const t = timeScale.pxToTime(x);
    117     const depth = Math.floor(y / SLICE_HEIGHT);
    118     for (let i = 0; i < data.starts.length; i++) {
    119       const tStart = data.starts[i];
    120       const tEnd = data.ends[i];
    121       const titleId = data.titles[i];
    122       if (tStart <= t && t <= tEnd && depth === data.depths[i]) {
    123         this.hoveredTitleId = titleId;
    124         break;
    125       }
    126     }
    127   }
    128 
    129   onMouseOut() {
    130     this.hoveredTitleId = -1;
    131   }
    132 
    133   getHeight() {
    134     return SLICE_HEIGHT * (this.config.maxDepth + 1) + 2 * TRACK_PADDING;
    135   }
    136 }
    137 
    138 trackRegistry.register(ChromeSliceTrack);
    139