• 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 * as m from 'mithril';
16
17import {assertExists} from '../base/logging';
18import {TimeSpan, timeToString} from '../common/time';
19
20import {hueForCpu} from './colorizer';
21import {DragGestureHandler} from './drag_gesture_handler';
22import {globals} from './globals';
23import {Panel, PanelSize} from './panel';
24import {TimeScale} from './time_scale';
25
26export class OverviewTimelinePanel extends Panel {
27  private width = 0;
28  private dragStartPx = 0;
29  private gesture?: DragGestureHandler;
30  private timeScale?: TimeScale;
31  private totTime = new TimeSpan(0, 0);
32
33  // Must explicitly type now; arguments types are no longer auto-inferred.
34  // https://github.com/Microsoft/TypeScript/issues/1373
35  onupdate({dom}: m.CVnodeDOM) {
36    this.width = dom.getBoundingClientRect().width;
37    this.totTime = new TimeSpan(
38        globals.state.traceTime.startSec, globals.state.traceTime.endSec);
39    this.timeScale = new TimeScale(this.totTime, [0, assertExists(this.width)]);
40
41    if (this.gesture === undefined) {
42      this.gesture = new DragGestureHandler(
43          dom as HTMLElement,
44          this.onDrag.bind(this),
45          this.onDragStart.bind(this),
46          this.onDragEnd.bind(this));
47    }
48  }
49
50  oncreate(vnode: m.CVnodeDOM) {
51    this.onupdate(vnode);
52  }
53
54  view() {
55    return m('.overview-timeline');
56  }
57
58  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
59    if (this.width === undefined) return;
60    if (this.timeScale === undefined) return;
61    const headerHeight = 25;
62    const tracksHeight = size.height - headerHeight;
63
64    // Draw time labels on the top header.
65    ctx.font = '10px Google Sans';
66    ctx.fillStyle = '#999';
67    for (let i = 0; i < 100; i++) {
68      const xPos = i * this.width / 100;
69      const t = this.timeScale.pxToTime(xPos);
70      if (xPos <= 0) continue;
71      if (xPos > this.width) break;
72      if (i % 10 === 0) {
73        ctx.fillRect(xPos, 0, 1, headerHeight - 5);
74        ctx.fillText(timeToString(t - this.totTime.start), xPos + 5, 18);
75      } else {
76        ctx.fillRect(xPos, 0, 1, 5);
77      }
78    }
79
80    // Draw mini-tracks with quanitzed density for each process.
81    if (globals.overviewStore.size > 0) {
82      const numTracks = globals.overviewStore.size;
83      let y = 0;
84      const trackHeight = (tracksHeight - 1) / numTracks;
85      for (const key of globals.overviewStore.keys()) {
86        const loads = globals.overviewStore.get(key)!;
87        for (let i = 0; i < loads.length; i++) {
88          const xStart = Math.floor(this.timeScale.timeToPx(loads[i].startSec));
89          const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].endSec));
90          const yOff = Math.floor(headerHeight + y * trackHeight);
91          const lightness = Math.ceil((1 - loads[i].load * 0.7) * 100);
92          ctx.fillStyle = `hsl(${hueForCpu(y)}, 50%, ${lightness}%)`;
93          ctx.fillRect(xStart, yOff, xEnd - xStart, Math.ceil(trackHeight));
94        }
95        y++;
96      }
97    }
98
99    // Draw bottom border.
100    ctx.fillStyle = 'hsl(219, 40%, 50%)';
101    ctx.fillRect(0, size.height - 1, this.width, 1);
102
103    // Draw semi-opaque rects that occlude the non-visible time range.
104    const vizTime = globals.frontendLocalState.visibleWindowTime;
105    const vizStartPx = Math.floor(this.timeScale.timeToPx(vizTime.start));
106    const vizEndPx = Math.ceil(this.timeScale.timeToPx(vizTime.end));
107
108    ctx.fillStyle = 'rgba(200, 200, 200, 0.8)';
109    ctx.fillRect(0, headerHeight, vizStartPx, tracksHeight);
110    ctx.fillRect(vizEndPx, headerHeight, this.width - vizEndPx, tracksHeight);
111
112    // Draw brushes.
113    ctx.fillStyle = '#333';
114    ctx.fillRect(vizStartPx - 1, headerHeight, 1, tracksHeight);
115    ctx.fillRect(vizEndPx, headerHeight, 1, tracksHeight);
116  }
117
118  onDrag(x: number) {
119    // Set visible time limits from selection.
120    if (this.timeScale === undefined) return;
121    let tStart = this.timeScale.pxToTime(this.dragStartPx);
122    let tEnd = this.timeScale.pxToTime(x);
123    if (tStart > tEnd) [tStart, tEnd] = [tEnd, tStart];
124    const vizTime = new TimeSpan(tStart, tEnd);
125    globals.frontendLocalState.updateVisibleTime(vizTime);
126    globals.rafScheduler.scheduleRedraw();
127  }
128
129  onDragStart(x: number) {
130    this.dragStartPx = x;
131  }
132
133  onDragEnd() {
134    this.dragStartPx = 0;
135  }
136}
137