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 {searchEq, searchRange, searchSegment} from '../../base/binary_search';
16import {assertTrue} from '../../base/logging';
17import {colorForThread} from '../../common/colorizer';
18import {TrackState} from '../../common/state';
19import {checkerboardExcept} from '../../frontend/checkerboard';
20import {globals} from '../../frontend/globals';
21import {Track} from '../../frontend/track';
22import {trackRegistry} from '../../frontend/track_registry';
23
24import {
25  Config,
26  Data,
27  PROCESS_SCHEDULING_TRACK_KIND,
28} from './common';
29
30const MARGIN_TOP = 5;
31const RECT_HEIGHT = 30;
32const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
33
34class ProcessSchedulingTrack extends Track<Config, Data> {
35  static readonly kind = PROCESS_SCHEDULING_TRACK_KIND;
36  static create(trackState: TrackState): ProcessSchedulingTrack {
37    return new ProcessSchedulingTrack(trackState);
38  }
39
40  private mouseXpos?: number;
41  private utidHoveredInThisTrack = -1;
42
43  constructor(trackState: TrackState) {
44    super(trackState);
45  }
46
47  getHeight(): number {
48    return TRACK_HEIGHT;
49  }
50
51  renderCanvas(ctx: CanvasRenderingContext2D): void {
52    // TODO: fonts and colors should come from the CSS and not hardcoded here.
53    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
54    const data = this.data();
55
56    if (data === undefined) return;  // Can't possibly draw anything.
57
58    // If the cached trace slices don't fully cover the visible time range,
59    // show a gray rectangle with a "Loading..." label.
60    checkerboardExcept(
61        ctx,
62        this.getHeight(),
63        timeScale.timeToPx(visibleWindowTime.start),
64        timeScale.timeToPx(visibleWindowTime.end),
65        timeScale.timeToPx(data.start),
66        timeScale.timeToPx(data.end));
67
68    assertTrue(data.starts.length === data.ends.length);
69    assertTrue(data.starts.length === data.utids.length);
70
71    const rawStartIdx =
72        data.ends.findIndex(end => end >= visibleWindowTime.start);
73    const startIdx = rawStartIdx === -1 ? data.starts.length : rawStartIdx;
74
75    const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end);
76    const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx;
77
78    const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu);
79
80    for (let i = startIdx; i < endIdx; i++) {
81      const tStart = data.starts[i];
82      const tEnd = data.ends[i];
83      const utid = data.utids[i];
84      const cpu = data.cpus[i];
85
86      const rectStart = timeScale.timeToPx(tStart);
87      const rectEnd = timeScale.timeToPx(tEnd);
88      const rectWidth = rectEnd - rectStart;
89      if (rectWidth < 0.3) continue;
90
91      const threadInfo = globals.threads.get(utid);
92      const pid = (threadInfo ? threadInfo.pid : -1) || -1;
93
94      const isHovering = globals.frontendLocalState.hoveredUtid !== -1;
95      const isThreadHovered = globals.frontendLocalState.hoveredUtid === utid;
96      const isProcessHovered = globals.frontendLocalState.hoveredPid === pid;
97      const color = colorForThread(threadInfo);
98      if (isHovering && !isThreadHovered) {
99        if (!isProcessHovered) {
100          color.l = 90;
101          color.s = 0;
102        } else {
103          color.l = Math.min(color.l + 30, 80);
104          color.s -= 20;
105        }
106      } else {
107        color.l = Math.min(color.l + 10, 60);
108        color.s -= 20;
109      }
110      ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
111      const y = MARGIN_TOP + cpuTrackHeight * cpu + cpu;
112      ctx.fillRect(rectStart, y, rectEnd - rectStart, cpuTrackHeight);
113    }
114
115    const hoveredThread = globals.threads.get(this.utidHoveredInThisTrack);
116    if (hoveredThread !== undefined && this.mouseXpos !== undefined) {
117      const tidText = `T: ${hoveredThread.threadName} [${hoveredThread.tid}]`;
118      if (hoveredThread.pid) {
119        const pidText = `P: ${hoveredThread.procName} [${hoveredThread.pid}]`;
120        this.drawTrackHoverTooltip(ctx, this.mouseXpos, pidText, tidText);
121      } else {
122        this.drawTrackHoverTooltip(ctx, this.mouseXpos, tidText);
123      }
124    }
125  }
126
127
128  onMouseMove({x, y}: {x: number, y: number}) {
129    const data = this.data();
130    this.mouseXpos = x;
131    if (data === undefined) return;
132    if (y < MARGIN_TOP || y > MARGIN_TOP + RECT_HEIGHT) {
133      this.utidHoveredInThisTrack = -1;
134      globals.frontendLocalState.setHoveredUtidAndPid(-1, -1);
135      return;
136    }
137
138    const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu);
139    const cpu = Math.floor((y - MARGIN_TOP) / (cpuTrackHeight + 1));
140    const {timeScale} = globals.frontendLocalState;
141    const t = timeScale.pxToTime(x);
142
143    const [i, j] = searchRange(data.starts, t, searchEq(data.cpus, cpu));
144    if (i === j || i >= data.starts.length || t > data.ends[i]) {
145      this.utidHoveredInThisTrack = -1;
146      globals.frontendLocalState.setHoveredUtidAndPid(-1, -1);
147      return;
148    }
149
150    const utid = data.utids[i];
151    this.utidHoveredInThisTrack = utid;
152    const threadInfo = globals.threads.get(utid);
153    const pid = threadInfo ? (threadInfo.pid ? threadInfo.pid : -1) : -1;
154    globals.frontendLocalState.setHoveredUtidAndPid(utid, pid);
155  }
156
157  onMouseOut() {
158    this.utidHoveredInThisTrack = -1;
159    globals.frontendLocalState.setHoveredUtidAndPid(-1, -1);
160    this.mouseXpos = 0;
161  }
162}
163
164trackRegistry.register(ProcessSchedulingTrack);
165