• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2020 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 {hsluvToHex} from 'hsluv';
16
17import {searchSegment} from '../../base/binary_search';
18import {Actions} from '../../common/actions';
19import {hslForSlice} from '../../common/colorizer';
20import {fromNs, toNs} from '../../common/time';
21import {globals} from '../../frontend/globals';
22import {TimeScale} from '../../frontend/time_scale';
23import {NewTrackArgs, Track} from '../../frontend/track';
24import {trackRegistry} from '../../frontend/track_registry';
25
26import {Config, CPU_PROFILE_TRACK_KIND, Data} from './common';
27
28const BAR_HEIGHT = 3;
29const MARGIN_TOP = 4.5;
30const RECT_HEIGHT = 30.5;
31
32function colorForSample(callsiteId: number, isHovered: boolean): string {
33  return hsluvToHex(hslForSlice(String(callsiteId), isHovered));
34}
35
36class CpuProfileTrack extends Track<Config, Data> {
37  static readonly kind = CPU_PROFILE_TRACK_KIND;
38  static create(args: NewTrackArgs): CpuProfileTrack {
39    return new CpuProfileTrack(args);
40  }
41
42  private centerY = this.getHeight() / 2 + BAR_HEIGHT;
43  private markerWidth = (this.getHeight() - MARGIN_TOP - BAR_HEIGHT) / 2;
44  private hoveredTs: number|undefined = undefined;
45
46  constructor(args: NewTrackArgs) {
47    super(args);
48  }
49
50  getHeight() {
51    return MARGIN_TOP + RECT_HEIGHT - 1;
52  }
53
54  renderCanvas(ctx: CanvasRenderingContext2D): void {
55    const {
56      timeScale,
57    } = globals.frontendLocalState;
58    const data = this.data();
59
60    if (data === undefined) return;
61
62    for (let i = 0; i < data.tsStarts.length; i++) {
63      const centerX = data.tsStarts[i];
64      const selection = globals.state.currentSelection;
65      const isHovered = this.hoveredTs === centerX;
66      const isSelected = selection !== null &&
67          selection.kind === 'CPU_PROFILE_SAMPLE' && selection.ts === centerX;
68      const strokeWidth = isSelected ? 3 : 0;
69      this.drawMarker(
70          ctx,
71          timeScale.timeToPx(fromNs(centerX)),
72          this.centerY,
73          isHovered,
74          strokeWidth,
75          data.callsiteId[i]);
76    }
77
78    let startX = data.tsStarts.length ? data.tsStarts[0] : -1;
79    let endX = data.tsStarts.length ? data.tsStarts[0] : -1;
80    let lastCallsiteId = data.callsiteId.length ? data.callsiteId[0] : -1;
81    for (let i = 0; i < data.tsStarts.length; i++) {
82      const centerX = data.tsStarts[i];
83      const callsiteId = data.callsiteId[i];
84      if (lastCallsiteId !== callsiteId) {
85        if (startX !== endX) {
86          const leftPx = timeScale.timeToPx(fromNs(startX)) - this.markerWidth;
87          const rightPx = timeScale.timeToPx(fromNs(endX)) + this.markerWidth;
88          const width = rightPx - leftPx;
89          ctx.fillStyle = colorForSample(lastCallsiteId, false);
90          ctx.fillRect(leftPx, MARGIN_TOP, width, BAR_HEIGHT);
91        }
92        startX = centerX;
93      }
94      endX = centerX;
95      lastCallsiteId = callsiteId;
96    }
97  }
98
99  drawMarker(
100      ctx: CanvasRenderingContext2D, x: number, y: number, isHovered: boolean,
101      strokeWidth: number, callsiteId: number): void {
102    ctx.beginPath();
103    ctx.moveTo(x - this.markerWidth, y - this.markerWidth);
104    ctx.lineTo(x, y + this.markerWidth);
105    ctx.lineTo(x + this.markerWidth, y - this.markerWidth);
106    ctx.lineTo(x - this.markerWidth, y - this.markerWidth);
107    ctx.closePath();
108    ctx.fillStyle = colorForSample(callsiteId, isHovered);
109    ctx.fill();
110    if (strokeWidth > 0) {
111      ctx.strokeStyle = colorForSample(callsiteId, false);
112      ctx.lineWidth = strokeWidth;
113      ctx.stroke();
114    }
115  }
116
117  onMouseMove({x, y}: {x: number, y: number}) {
118    const data = this.data();
119    if (data === undefined) return;
120    const {timeScale} = globals.frontendLocalState;
121    const time = toNs(timeScale.pxToTime(x));
122    const [left, right] = searchSegment(data.tsStarts, time);
123    const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
124    this.hoveredTs = index === -1 ? undefined : data.tsStarts[index];
125  }
126
127  onMouseOut() {
128    this.hoveredTs = undefined;
129  }
130
131  onMouseClick({x, y}: {x: number, y: number}) {
132    const data = this.data();
133    if (data === undefined) return false;
134    const {timeScale} = globals.frontendLocalState;
135
136    const time = toNs(timeScale.pxToTime(x));
137    const [left, right] = searchSegment(data.tsStarts, time);
138
139    const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
140
141    if (index !== -1) {
142      const id = data.ids[index];
143      const ts = data.tsStarts[index];
144
145      globals.makeSelection(
146          Actions.selectCpuProfileSample({id, utid: this.config.utid, ts}));
147      return true;
148    }
149    return false;
150  }
151
152  // If the markers overlap the rightmost one will be selected.
153  findTimestampIndex(
154      left: number, timeScale: TimeScale, data: Data, x: number, y: number,
155      right: number): number {
156    let index = -1;
157    if (left !== -1) {
158      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[left]));
159      if (this.isInMarker(x, y, centerX)) {
160        index = left;
161      }
162    }
163    if (right !== -1) {
164      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[right]));
165      if (this.isInMarker(x, y, centerX)) {
166        index = right;
167      }
168    }
169    return index;
170  }
171
172  isInMarker(x: number, y: number, centerX: number) {
173    return Math.abs(x - centerX) + Math.abs(y - this.centerY) <=
174        this.markerWidth;
175  }
176}
177
178trackRegistry.register(CpuProfileTrack);
179