• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15import {cropText} from '../../common/canvas_utils';
16import {TrackState} from '../../common/state';
17import {checkerboardExcept} from '../../frontend/checkerboard';
18import {globals} from '../../frontend/globals';
19import {Track} from '../../frontend/track';
20import {trackRegistry} from '../../frontend/track_registry';
21
22import {Config, Data, SLICE_TRACK_KIND} from './common';
23
24const SLICE_HEIGHT = 20;
25const TRACK_PADDING = 5;
26
27function 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
36class 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
138trackRegistry.register(ChromeSliceTrack);
139