blob: 647d3c9d804f1bf88f78b6e9e37660c6c136b401 [file] [log] [blame]
Deepanjan Royfd79a7d2018-07-24 13:26:55 -04001// 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
Isabelle Taylord99a5d72019-03-25 16:45:32 +000017import {Actions} from '../common/actions';
Hector Dearman79549c52019-04-01 13:30:46 +010018import {LogExists, LogExistsKey} from '../common/logs';
Primiano Tucci8afc06d2018-08-06 19:11:42 +010019import {QueryResponse} from '../common/queries';
Primiano Tuccif30cd9c2018-08-13 01:53:26 +020020import {TimeSpan} from '../common/time';
Primiano Tucci8afc06d2018-08-06 19:11:42 +010021
Nicolò Mazzucato87943de2019-07-25 16:26:33 +010022import {ChromeSliceDetailsPanel} from './chrome_slice_panel';
Hector Dearmana89177f2018-10-04 15:42:59 +010023import {copyToClipboard} from './clipboard';
Neda Topoljanac643dbaa2019-09-11 12:06:56 +010024import {CounterDetailsPanel} from './counter_panel';
Hector Dearmanafa51b22018-12-11 16:38:30 +000025import {DragGestureHandler} from './drag_gesture_handler';
Primiano Tucci8afc06d2018-08-06 19:11:42 +010026import {globals} from './globals';
Hector Dearman79549c52019-04-01 13:30:46 +010027import {LogPanel} from './logs_panel';
Hector Dearmanea002ea2019-01-21 11:43:45 +000028import {NotesEditorPanel, NotesPanel} from './notes_panel';
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040029import {OverviewTimelinePanel} from './overview_timeline_panel';
Deepanjan Royfd79a7d2018-07-24 13:26:55 -040030import {createPage} from './pages';
Michail Schwab405002c2018-07-26 13:19:10 -040031import {PanAndZoomHandler} from './pan_and_zoom_handler';
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -040032import {Panel} from './panel';
33import {AnyAttrsVnode, PanelContainer} from './panel_container';
Isabelle Taylord99a5d72019-03-25 16:45:32 +000034import {SliceDetailsPanel} from './slice_panel';
35import {ThreadStatePanel} from './thread_state_panel';
Hector Dearman372ec602019-09-03 12:12:02 +010036import {TickmarkPanel} from './tickmark_panel';
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040037import {TimeAxisPanel} from './time_axis_panel';
Hector Dearman3e82d682019-01-09 15:55:10 +000038import {computeZoom} from './time_scale';
Isabelle Taylor8325c5a2019-02-13 13:38:17 +000039import {TimeSelectionPanel} from './time_selection_panel';
Hector Dearmanccb0b792019-01-22 13:19:41 +000040import {TRACK_SHELL_WIDTH} from './track_constants';
Deepanjan Roy32ad0d72018-10-18 14:50:16 -040041import {TrackGroupPanel} from './track_group_panel';
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040042import {TrackPanel} from './track_panel';
Kodi Obika9adb0582019-06-12 09:36:03 -070043import {VideoPanel} from './video_panel';
Deepanjan Royfd79a7d2018-07-24 13:26:55 -040044
Isabelle Tayloree67d1b2019-02-27 14:12:53 +000045const DRAG_HANDLE_HEIGHT_PX = 28;
Isabelle Taylorb6257fc2019-02-19 13:13:32 +000046const DEFAULT_DETAILS_HEIGHT_PX = 230 + DRAG_HANDLE_HEIGHT_PX;
Isabelle Tayloree67d1b2019-02-27 14:12:53 +000047const UP_ICON = 'keyboard_arrow_up';
48const DOWN_ICON = 'keyboard_arrow_down';
Isabelle Taylorf57b24f2019-07-31 11:06:41 +010049const SIDEBAR_WIDTH = 256;
Primiano Tucci8afc06d2018-08-06 19:11:42 +010050
Hector Dearman79549c52019-04-01 13:30:46 +010051function hasLogs(): boolean {
52 const data = globals.trackDataStore.get(LogExistsKey) as LogExists;
53 return data && data.exists;
54}
55
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -040056class QueryTable extends Panel {
Primiano Tucci8afc06d2018-08-06 19:11:42 +010057 view() {
58 const resp = globals.queryResults.get('command') as QueryResponse;
59 if (resp === undefined) {
60 return m('');
61 }
62 const cols = [];
63 for (const col of resp.columns) {
64 cols.push(m('td', col));
65 }
66 const header = m('tr', cols);
67
68 const rows = [];
69 for (let i = 0; i < resp.rows.length; i++) {
70 const cells = [];
71 for (const col of resp.columns) {
72 cells.push(m('td', resp.rows[i][col]));
73 }
74 rows.push(m('tr', cells));
75 }
76 return m(
77 'div',
78 m('header.overview',
Hector Dearmanb1179f92019-01-08 16:18:56 +000079 `Query result - ${Math.round(resp.durationMs)} ms`,
80 m('span.code', resp.query),
Hector Dearmana89177f2018-10-04 15:42:59 +010081 resp.error ? null :
Hector Dearmanb1179f92019-01-08 16:18:56 +000082 m('button.query-ctrl',
Hector Dearmana89177f2018-10-04 15:42:59 +010083 {
84 onclick: () => {
85 const lines: string[][] = [];
86 lines.push(resp.columns);
87 for (const row of resp.rows) {
88 const line = [];
89 for (const col of resp.columns) {
90 line.push(row[col].toString());
91 }
92 lines.push(line);
93 }
94 copyToClipboard(
95 lines.map(line => line.join('\t')).join('\n'));
96 },
97 },
Hector Dearmanb1179f92019-01-08 16:18:56 +000098 'Copy as .tsv'),
99 m('button.query-ctrl',
100 {
101 onclick: () => {
102 globals.queryResults.delete('command');
103 globals.rafScheduler.scheduleFullRedraw();
104 }
105 },
106 'Close'), ),
Primiano Tucci8afc06d2018-08-06 19:11:42 +0100107 resp.error ?
108 m('.query-error', `SQL error: ${resp.error}`) :
109 m('table.query-table', m('thead', header), m('tbody', rows)));
Deepanjan Roy97f63242018-09-20 15:32:01 -0400110 }
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -0400111
112 renderCanvas() {}
Deepanjan Roy97f63242018-09-20 15:32:01 -0400113}
Primiano Tucci8afc06d2018-08-06 19:11:42 +0100114
Hector Dearmanafa51b22018-12-11 16:38:30 +0000115interface DragHandleAttrs {
116 height: number;
117 resize: (height: number) => void;
118}
119
120class DragHandle implements m.ClassComponent<DragHandleAttrs> {
121 private dragStartHeight = 0;
122 private height = 0;
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000123 private resize: (height: number) => void = () => {};
Hector Dearman79549c52019-04-01 13:30:46 +0100124 private isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
Hector Dearmanafa51b22018-12-11 16:38:30 +0000125
126 oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
127 this.resize = attrs.resize;
128 this.height = attrs.height;
Isabelle Taylorcc850612019-08-19 14:15:20 +0100129 this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
Hector Dearmanafa51b22018-12-11 16:38:30 +0000130 const elem = dom as HTMLElement;
131 new DragGestureHandler(
132 elem,
133 this.onDrag.bind(this),
134 this.onDragStart.bind(this),
135 this.onDragEnd.bind(this));
136 }
137
138 onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
139 this.resize = attrs.resize;
140 this.height = attrs.height;
Isabelle Taylorcc850612019-08-19 14:15:20 +0100141 this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
Hector Dearmanafa51b22018-12-11 16:38:30 +0000142 }
143
144 onDrag(_x: number, y: number) {
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000145 const newHeight = this.dragStartHeight + (DRAG_HANDLE_HEIGHT_PX / 2) - y;
146 this.isClosed = Math.floor(newHeight) <= DRAG_HANDLE_HEIGHT_PX;
147 this.resize(Math.floor(newHeight));
Hector Dearmanafa51b22018-12-11 16:38:30 +0000148 globals.rafScheduler.scheduleFullRedraw();
149 }
150
151 onDragStart(_x: number, _y: number) {
152 this.dragStartHeight = this.height;
153 }
154
155 onDragEnd() {}
156
157 view() {
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000158 const icon = this.isClosed ? UP_ICON : DOWN_ICON;
159 const title = this.isClosed ? 'Show panel' : 'Hide panel';
160 return m(
161 '.handle',
162 m('.handle-title', 'Current Selection'),
163 m('i.material-icons',
164 {
165 onclick: () => {
166 if (this.height === DRAG_HANDLE_HEIGHT_PX) {
167 this.isClosed = false;
168 this.resize(DEFAULT_DETAILS_HEIGHT_PX);
169 } else {
170 this.isClosed = true;
171 this.resize(DRAG_HANDLE_HEIGHT_PX);
172 }
173 globals.rafScheduler.scheduleFullRedraw();
174 },
175 title
176 },
177 icon));
Hector Dearmanafa51b22018-12-11 16:38:30 +0000178 }
179}
180
Deepanjan Royfd79a7d2018-07-24 13:26:55 -0400181/**
182 * Top-most level component for the viewer page. Holds tracks, brush timeline,
183 * panels, and everything else that's part of the main trace viewer page.
184 */
Deepanjan Roy97f63242018-09-20 15:32:01 -0400185class TraceViewer implements m.ClassComponent {
186 private onResize: () => void = () => {};
187 private zoomContent?: PanAndZoomHandler;
Hector Dearman79549c52019-04-01 13:30:46 +0100188 private detailsHeight = DRAG_HANDLE_HEIGHT_PX;
Isabelle Taylor0733c8c2019-02-07 13:55:23 +0000189 // Used to set details panel to default height on selection.
Hector Dearman79549c52019-04-01 13:30:46 +0100190 private showDetailsPanel = true;
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000191 // Used to prevent global deselection if a pan/drag select occurred.
192 private keepCurrentSelection = false;
Hector Dearman59e98122018-08-13 14:21:38 +0100193
Deepanjan Roy97f63242018-09-20 15:32:01 -0400194 oncreate(vnode: m.CVnodeDOM) {
Deepanjan Roy9d95a252018-08-09 10:10:19 -0400195 const frontendLocalState = globals.frontendLocalState;
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400196 const updateDimensions = () => {
Michail Schwab405002c2018-07-26 13:19:10 -0400197 const rect = vnode.dom.getBoundingClientRect();
Deepanjan Roy9d95a252018-08-09 10:10:19 -0400198 frontendLocalState.timeScale.setLimitsPx(
Deepanjan Roy97f63242018-09-20 15:32:01 -0400199 0, rect.width - TRACK_SHELL_WIDTH);
Michail Schwab405002c2018-07-26 13:19:10 -0400200 };
201
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400202 updateDimensions();
203
204 // TODO: Do resize handling better.
205 this.onResize = () => {
206 updateDimensions();
207 globals.rafScheduler.scheduleFullRedraw();
208 };
Michail Schwab405002c2018-07-26 13:19:10 -0400209
210 // Once ResizeObservers are out, we can stop accessing the window here.
211 window.addEventListener('resize', this.onResize);
212
Michail Schwaba52306a2018-07-31 13:40:52 -0400213 const panZoomEl =
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400214 vnode.dom.querySelector('.pan-and-zoom-content') as HTMLElement;
Michail Schwaba52306a2018-07-31 13:40:52 -0400215
Michail Schwab405002c2018-07-26 13:19:10 -0400216 this.zoomContent = new PanAndZoomHandler({
Michail Schwaba52306a2018-07-31 13:40:52 -0400217 element: panZoomEl,
Isabelle Taylorf57b24f2019-07-31 11:06:41 +0100218 contentOffsetX: SIDEBAR_WIDTH,
Michail Schwab405002c2018-07-26 13:19:10 -0400219 onPanned: (pannedPx: number) => {
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000220 this.keepCurrentSelection = true;
Primiano Tuccie36ca632018-08-21 14:32:23 +0200221 const traceTime = globals.state.traceTime;
222 const vizTime = globals.frontendLocalState.visibleWindowTime;
223 const origDelta = vizTime.duration;
224 const tDelta = frontendLocalState.timeScale.deltaPxToDuration(pannedPx);
225 let tStart = vizTime.start + tDelta;
226 let tEnd = vizTime.end + tDelta;
227 if (tStart < traceTime.startSec) {
228 tStart = traceTime.startSec;
229 tEnd = tStart + origDelta;
230 } else if (tEnd > traceTime.endSec) {
231 tEnd = traceTime.endSec;
232 tStart = tEnd - origDelta;
Primiano Tuccif30cd9c2018-08-13 01:53:26 +0200233 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200234 frontendLocalState.updateVisibleTime(new TimeSpan(tStart, tEnd));
Hector Dearman2ba11332018-10-18 14:32:43 +0100235 globals.rafScheduler.scheduleRedraw();
Michail Schwab405002c2018-07-26 13:19:10 -0400236 },
Hector Dearman3e82d682019-01-09 15:55:10 +0000237 onZoomed: (zoomedPositionPx: number, zoomRatio: number) => {
238 // TODO(hjd): Avoid hardcoding TRACK_SHELL_WIDTH.
239 // TODO(hjd): Improve support for zooming in overview timeline.
240 const span = frontendLocalState.visibleWindowTime;
241 const scale = frontendLocalState.timeScale;
242 const zoomPx = zoomedPositionPx - TRACK_SHELL_WIDTH;
243 const newSpan = computeZoom(scale, span, 1 - zoomRatio, zoomPx);
244 frontendLocalState.updateVisibleTime(newSpan);
245 globals.rafScheduler.scheduleRedraw();
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000246 },
247 onDragSelect: (selectStartPx: number|null, selectEndPx: number) => {
248 if (!selectStartPx) return;
249 this.keepCurrentSelection = true;
Isabelle Taylor1ae33bc2019-02-22 11:44:33 +0000250 globals.frontendLocalState.setShowTimeSelectPreview(false);
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000251 const traceTime = globals.state.traceTime;
252 const scale = frontendLocalState.timeScale;
253 const startPx = Math.min(selectStartPx, selectEndPx);
254 const endPx = Math.max(selectStartPx, selectEndPx);
255 const startTs = Math.max(traceTime.startSec,
256 scale.pxToTime(startPx - TRACK_SHELL_WIDTH));
257 const endTs = Math.min(traceTime.endSec,
258 scale.pxToTime(endPx - TRACK_SHELL_WIDTH));
Isabelle Taylor685559d2019-09-05 15:20:10 +0100259 globals.makeSelection(Actions.selectTimeSpan({startTs, endTs}));
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000260 globals.rafScheduler.scheduleRedraw();
Michail Schwab405002c2018-07-26 13:19:10 -0400261 }
Deepanjan Royfd79a7d2018-07-24 13:26:55 -0400262 });
Deepanjan Roy97f63242018-09-20 15:32:01 -0400263 }
Hector Dearman59e98122018-08-13 14:21:38 +0100264
Michail Schwab405002c2018-07-26 13:19:10 -0400265 onremove() {
266 window.removeEventListener('resize', this.onResize);
Deepanjan Roy97f63242018-09-20 15:32:01 -0400267 if (this.zoomContent) this.zoomContent.shutdown();
268 }
Hector Dearman59e98122018-08-13 14:21:38 +0100269
Michail Schwab405002c2018-07-26 13:19:10 -0400270 view() {
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -0400271 const scrollingPanels: AnyAttrsVnode[] =
Hector Dearman9189b6b2019-02-27 14:58:51 +0000272 globals.state.scrollingTracks.map(id => m(TrackPanel, {key: id, id}));
Deepanjan Roy32ad0d72018-10-18 14:50:16 -0400273
274 for (const group of Object.values(globals.state.trackGroups)) {
275 scrollingPanels.push(m(TrackGroupPanel, {
276 trackGroupId: group.id,
277 key: `trackgroup-${group.id}`,
278 }));
279 if (group.collapsed) continue;
280 for (const trackId of group.tracks) {
281 scrollingPanels.push(m(TrackPanel, {
282 key: `track-${group.id}-${trackId}`,
283 id: trackId,
284 }));
285 }
286 }
Isabelle Taylor1acfa2b2019-05-17 17:26:40 +0100287 scrollingPanels.unshift(m(QueryTable, {key: 'query'}));
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -0400288
Hector Dearmanea002ea2019-01-21 11:43:45 +0000289 const detailsPanels: AnyAttrsVnode[] = [];
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000290 const curSelection = globals.state.currentSelection;
291 if (curSelection) {
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000292 switch (curSelection.kind) {
Isabelle Taylor9a4afc62019-02-06 11:54:36 +0000293 case 'NOTE':
294 detailsPanels.push(m(NotesEditorPanel, {
295 key: 'notes',
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000296 id: curSelection.id,
Isabelle Taylor9a4afc62019-02-06 11:54:36 +0000297 }));
298 break;
299 case 'SLICE':
300 detailsPanels.push(m(SliceDetailsPanel, {
301 key: 'slice',
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000302 utid: curSelection.utid,
Isabelle Taylor9a4afc62019-02-06 11:54:36 +0000303 }));
304 break;
Neda Topoljanac643dbaa2019-09-11 12:06:56 +0100305 case 'COUNTER':
306 detailsPanels.push(m(CounterDetailsPanel, {
307 key: 'counter',
308 }));
309 break;
Nicolò Mazzucato87943de2019-07-25 16:26:33 +0100310 case 'CHROME_SLICE':
311 detailsPanels.push(m(ChromeSliceDetailsPanel));
312 break;
Isabelle Taylord99a5d72019-03-25 16:45:32 +0000313 case 'THREAD_STATE':
314 detailsPanels.push(m(ThreadStatePanel, {
315 key: 'thread_state',
316 ts: curSelection.ts,
317 dur: curSelection.dur,
318 utid: curSelection.utid,
Isabelle Taylor9d3b9942019-09-03 13:18:19 +0100319 state: curSelection.state,
320 cpu: curSelection.cpu
Isabelle Taylord99a5d72019-03-25 16:45:32 +0000321 }));
322 break;
Isabelle Taylor9a4afc62019-02-06 11:54:36 +0000323 default:
324 break;
325 }
Hector Dearman79549c52019-04-01 13:30:46 +0100326 } else if (hasLogs()) {
327 detailsPanels.push(m(LogPanel, {}));
Isabelle Taylorb9222c32019-01-31 10:58:37 +0000328 }
329
Isabelle Taylorcc850612019-08-19 14:15:20 +0100330 const wasShowing = this.showDetailsPanel;
Hector Dearman79549c52019-04-01 13:30:46 +0100331 this.showDetailsPanel = detailsPanels.length > 0;
Isabelle Taylorcc850612019-08-19 14:15:20 +0100332 // Pop up details panel on first selection.
333 if (!wasShowing && this.showDetailsPanel &&
334 this.detailsHeight === DRAG_HANDLE_HEIGHT_PX) {
335 this.detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
336 }
Hector Dearman79549c52019-04-01 13:30:46 +0100337
Michail Schwabc834e482018-07-26 13:30:13 -0400338 return m(
Primiano Tucci8afc06d2018-08-06 19:11:42 +0100339 '.page',
Kodi Obika9adb0582019-06-12 09:36:03 -0700340 m('.split-panel',
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100341 m('.pan-and-zoom-content',
342 {
343 onclick: () => {
344 // We don't want to deselect when panning/drag selecting.
345 if (this.keepCurrentSelection) {
346 this.keepCurrentSelection = false;
347 return;
348 }
Isabelle Taylor061c8712019-09-16 13:38:58 +0100349 globals.makeSelection(Actions.deselect({}));
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000350 }
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100351 },
352 m('.pinned-panel-container', m(PanelContainer, {
353 doesScroll: false,
354 panels: [
355 m(OverviewTimelinePanel, {key: 'overview'}),
356 m(TimeAxisPanel, {key: 'timeaxis'}),
357 m(TimeSelectionPanel, {key: 'timeselection'}),
358 m(NotesPanel, {key: 'notes'}),
Hector Dearman372ec602019-09-03 12:12:02 +0100359 m(TickmarkPanel, {key: 'searchTickmarks'}),
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100360 ...globals.state.pinnedTracks.map(
361 id => m(TrackPanel, {key: id, id})),
362 ],
363 kind: 'OVERVIEW',
364 })),
365 m('.scrolling-panel-container', m(PanelContainer, {
366 doesScroll: true,
367 panels: scrollingPanels,
368 kind: 'TRACKS',
369 }))),
370 m('.video-panel',
371 (globals.state.videoEnabled && globals.state.video != null) ?
372 m(VideoPanel) :
373 null)),
Hector Dearmanafa51b22018-12-11 16:38:30 +0000374 m('.details-content',
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000375 {
376 style: {
377 height: `${this.detailsHeight}px`,
Hector Dearman79549c52019-04-01 13:30:46 +0100378 display: this.showDetailsPanel ? null : 'none'
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000379 }
380 },
Hector Dearmanafa51b22018-12-11 16:38:30 +0000381 m(DragHandle, {
382 resize: (height: number) => {
383 this.detailsHeight = Math.max(height, DRAG_HANDLE_HEIGHT_PX);
384 },
385 height: this.detailsHeight,
Hector Dearmanea002ea2019-01-21 11:43:45 +0000386 }),
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000387 m('.details-panel-container',
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100388 m(PanelContainer,
389 {doesScroll: true, panels: detailsPanels, kind: 'DETAILS'}))));
Deepanjan Roy97f63242018-09-20 15:32:01 -0400390 }
391}
Deepanjan Royfd79a7d2018-07-24 13:26:55 -0400392
393export const ViewerPage = createPage({
394 view() {
395 return m(TraceViewer);
396 }
Hector Dearman64484b92018-07-30 16:14:43 +0100397});