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