• 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 {assertUnreachable} from '../base/logging';
16import {Time, time, TimeSpan} from '../base/time';
17import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
18import {raf} from './raf_scheduler';
19import {HighPrecisionTime} from '../base/high_precision_time';
20import {DurationPrecision, Timeline, TimestampFormat} from '../public/timeline';
21import {
22  durationPrecision,
23  setDurationPrecision,
24  setTimestampFormat,
25  timestampFormat,
26} from './timestamp_format';
27import {TraceInfo} from '../public/trace_info';
28
29const MIN_DURATION = 10;
30
31/**
32 * State that is shared between several frontend components, but not the
33 * controller. This state is updated at 60fps.
34 */
35export class TimelineImpl implements Timeline {
36  private _visibleWindow: HighPrecisionTimeSpan;
37  private _hoverCursorTimestamp?: time;
38  private _highlightedSliceId?: number;
39  private _hoveredNoteTimestamp?: time;
40
41  // TODO(stevegolton): These are currently only referenced by the cpu slice
42  // tracks and the process summary tracks. We should just make this a local
43  // property of the cpu slice tracks and ignore them in the process tracks.
44  private _hoveredUtid?: number;
45  private _hoveredPid?: number;
46
47  // This is used to mark the timeline of the area that is currently being
48  // selected.
49  //
50  // TODO(stevegolton): This shouldn't really be in the global timeline state,
51  // it's really only a concept of the viewer page and should be moved there
52  // instead.
53  selectedSpan?: {start: time; end: time};
54
55  get highlightedSliceId() {
56    return this._highlightedSliceId;
57  }
58
59  set highlightedSliceId(x) {
60    this._highlightedSliceId = x;
61    raf.scheduleCanvasRedraw();
62  }
63
64  get hoveredNoteTimestamp() {
65    return this._hoveredNoteTimestamp;
66  }
67
68  set hoveredNoteTimestamp(x) {
69    this._hoveredNoteTimestamp = x;
70    raf.scheduleCanvasRedraw();
71  }
72
73  get hoveredUtid() {
74    return this._hoveredUtid;
75  }
76
77  set hoveredUtid(x) {
78    this._hoveredUtid = x;
79    raf.scheduleCanvasRedraw();
80  }
81
82  get hoveredPid() {
83    return this._hoveredPid;
84  }
85
86  set hoveredPid(x) {
87    this._hoveredPid = x;
88    raf.scheduleCanvasRedraw();
89  }
90
91  constructor(private readonly traceInfo: TraceInfo) {
92    this._visibleWindow = HighPrecisionTimeSpan.fromTime(
93      traceInfo.start,
94      traceInfo.end,
95    );
96  }
97
98  // TODO: there is some redundancy in the fact that both |visibleWindowTime|
99  // and a |timeScale| have a notion of time range. That should live in one
100  // place only.
101
102  zoomVisibleWindow(ratio: number, centerPoint: number) {
103    this._visibleWindow = this._visibleWindow
104      .scale(ratio, centerPoint, MIN_DURATION)
105      .fitWithin(this.traceInfo.start, this.traceInfo.end);
106
107    raf.scheduleCanvasRedraw();
108  }
109
110  panVisibleWindow(delta: number) {
111    this._visibleWindow = this._visibleWindow
112      .translate(delta)
113      .fitWithin(this.traceInfo.start, this.traceInfo.end);
114
115    raf.scheduleCanvasRedraw();
116  }
117
118  // Given a timestamp, if |ts| is not currently in view move the view to
119  // center |ts|, keeping the same zoom level.
120  panToTimestamp(ts: time) {
121    if (this._visibleWindow.contains(ts)) return;
122    // TODO(hjd): This is an ugly jump, we should do a smooth pan instead.
123    const halfDuration = this.visibleWindow.duration / 2;
124    const newStart = new HighPrecisionTime(ts).subNumber(halfDuration);
125    const newWindow = new HighPrecisionTimeSpan(
126      newStart,
127      this._visibleWindow.duration,
128    );
129    this.updateVisibleTimeHP(newWindow);
130  }
131
132  // Set visible window using an integer time span
133  updateVisibleTime(ts: TimeSpan) {
134    this.updateVisibleTimeHP(HighPrecisionTimeSpan.fromTime(ts.start, ts.end));
135  }
136
137  // TODO(primiano): we ended up with two entry-points for the same function,
138  // unify them.
139  setViewportTime(start: time, end: time): void {
140    this.updateVisibleTime(new TimeSpan(start, end));
141  }
142
143  moveStart(delta: number) {
144    this.updateVisibleTimeHP(
145      new HighPrecisionTimeSpan(
146        this._visibleWindow.start.addNumber(delta),
147        this.visibleWindow.duration - delta,
148      ),
149    );
150  }
151
152  moveEnd(delta: number) {
153    this.updateVisibleTimeHP(
154      new HighPrecisionTimeSpan(
155        this._visibleWindow.start,
156        this.visibleWindow.duration + delta,
157      ),
158    );
159  }
160
161  // Set visible window using a high precision time span
162  updateVisibleTimeHP(ts: HighPrecisionTimeSpan) {
163    this._visibleWindow = ts
164      .clampDuration(MIN_DURATION)
165      .fitWithin(this.traceInfo.start, this.traceInfo.end);
166
167    raf.scheduleCanvasRedraw();
168  }
169
170  // Get the bounds of the visible window as a high-precision time span
171  get visibleWindow(): HighPrecisionTimeSpan {
172    return this._visibleWindow;
173  }
174
175  get hoverCursorTimestamp(): time | undefined {
176    return this._hoverCursorTimestamp;
177  }
178
179  set hoverCursorTimestamp(t: time | undefined) {
180    this._hoverCursorTimestamp = t;
181    raf.scheduleCanvasRedraw();
182  }
183
184  // Offset between t=0 and the configured time domain.
185  timestampOffset(): time {
186    const fmt = timestampFormat();
187    switch (fmt) {
188      case TimestampFormat.Timecode:
189      case TimestampFormat.Seconds:
190      case TimestampFormat.Milliseconds:
191      case TimestampFormat.Microseconds:
192        return this.traceInfo.start;
193      case TimestampFormat.TraceNs:
194      case TimestampFormat.TraceNsLocale:
195        return Time.ZERO;
196      case TimestampFormat.UTC:
197        return this.traceInfo.utcOffset;
198      case TimestampFormat.TraceTz:
199        return this.traceInfo.traceTzOffset;
200      default:
201        assertUnreachable(fmt);
202    }
203  }
204
205  // Convert absolute time to domain time.
206  toDomainTime(ts: time): time {
207    return Time.sub(ts, this.timestampOffset());
208  }
209
210  get timestampFormat() {
211    return timestampFormat();
212  }
213
214  set timestampFormat(format: TimestampFormat) {
215    setTimestampFormat(format);
216  }
217
218  get durationPrecision() {
219    return durationPrecision();
220  }
221
222  set durationPrecision(precision: DurationPrecision) {
223    setDurationPrecision(precision);
224  }
225}
226