• 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 {assertTrue} from '../base/logging';
16import {PivotTree} from '../controller/pivot_table_redux_controller';
17import {RecordConfig} from '../controller/record_config_types';
18
19import {
20  AggregationAttrs,
21  PivotAttrs,
22  SubQueryAttrs,
23  TableAttrs
24} from './pivot_table_common';
25
26/**
27 * A plain js object, holding objects of type |Class| keyed by string id.
28 * We use this instead of using |Map| object since it is simpler and faster to
29 * serialize for use in postMessage.
30 */
31export interface ObjectById<Class extends{id: string}> { [id: string]: Class; }
32
33export type Timestamped<T> = {
34  [P in keyof T]: T[P];
35}&{lastUpdate: number};
36
37export type OmniboxMode = 'SEARCH'|'COMMAND';
38
39export type OmniboxState = Timestamped<{omnibox: string; mode: OmniboxMode}>;
40
41export type VisibleState =
42    Timestamped<{startSec: number; endSec: number; resolution: number;}>;
43
44export interface AreaSelection {
45  kind: 'AREA';
46  areaId: string;
47  // When an area is marked it will be assigned a unique note id and saved as
48  // an AreaNote for the user to return to later. id = 0 is the special id that
49  // is overwritten when a new area is marked. Any other id is a persistent
50  // marking that will not be overwritten.
51  // When not set, the area selection will be replaced with any
52  // new area selection (i.e. not saved anywhere).
53  noteId?: string;
54}
55
56export type AreaById = Area&{id: string};
57
58export interface Area {
59  startSec: number;
60  endSec: number;
61  tracks: string[];
62}
63
64export const MAX_TIME = 180;
65
66// 3: TrackKindPriority and related sorting changes.
67// 5: Move a large number of items off frontendLocalState and onto state.
68// 6: Common PivotTableConfig and pivot table specific PivotTableState.
69// 7: Split Chrome categories in two and add 'symbolize ksyms' flag.
70// 8: Rename several variables
71// "[...]HeapProfileFlamegraph[...]" -> "[...]Flamegraph[...]".
72// 9: Add a field to track last loaded recording profile name
73// 10: Change last loaded profile tracking type to accommodate auto-save.
74// 11: Rename updateChromeCategories to fetchChromeCategories.
75// 12: Add a field to cache mapping from UI track ID to trace track ID in order
76//     to speed up flow arrows rendering.
77// 13: FlamegraphState changed to support area selection.
78// 14: Changed the type of uiTrackIdByTraceTrackId from `Map` to an object with
79// typed key/value because a `Map` does not preserve type during
80// serialisation+deserialisation.
81// 15: Added state for Pivot Table V2
82// 16: Added boolean tracking if the flamegraph modal was dismissed
83export const STATE_VERSION = 16;
84
85export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
86
87export type EngineMode = 'WASM'|'HTTP_RPC';
88
89export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE'|'FORCE_BUILTIN_WASM';
90
91export enum TrackKindPriority {
92  'MAIN_THREAD' = 0,
93  'RENDER_THREAD' = 1,
94  'GPU_COMPLETION' = 2,
95  'CHROME_IO_THREAD' = 3,
96  'CHROME_COMPOSITOR' = 4,
97  'ORDINARY' = 5
98}
99
100export type FlamegraphStateViewingOption =
101    'SPACE'|'ALLOC_SPACE'|'OBJECTS'|'ALLOC_OBJECTS'|'PERF_SAMPLES';
102
103export interface CallsiteInfo {
104  id: number;
105  parentId: number;
106  depth: number;
107  name?: string;
108  totalSize: number;
109  selfSize: number;
110  mapping: string;
111  merged: boolean;
112  highlighted: boolean;
113  location?: string;
114}
115
116export interface TraceFileSource {
117  type: 'FILE';
118  file: File;
119}
120
121export interface TraceArrayBufferSource {
122  type: 'ARRAY_BUFFER';
123  buffer: ArrayBuffer;
124  title: string;
125  url?: string;
126  fileName?: string;
127
128  // |uuid| is set only when loading via ?local_cache_key=1234. When set,
129  // this matches global.state.traceUuid, with the exception of the following
130  // time window: When a trace T1 is loaded and the user loads another trace T2,
131  // this |uuid| will be == T2, but the globals.state.traceUuid will be
132  // temporarily == T1 until T2 has been loaded (consistently to what happens
133  // with all other state fields).
134  uuid?: string;
135  // if |localOnly| is true then the trace should not be shared or downloaded.
136  localOnly?: boolean;
137}
138
139export interface TraceUrlSource {
140  type: 'URL';
141  url: string;
142}
143
144export interface TraceHttpRpcSource {
145  type: 'HTTP_RPC';
146}
147
148export type TraceSource =
149    TraceFileSource|TraceArrayBufferSource|TraceUrlSource|TraceHttpRpcSource;
150
151export interface TrackState {
152  id: string;
153  engineId: string;
154  kind: string;
155  name: string;
156  labels?: string[];
157  trackKindPriority: TrackKindPriority;
158  trackGroup?: string;
159  config: {
160    trackId?: number;
161    trackIds?: number[];
162  };
163}
164
165export interface TrackGroupState {
166  id: string;
167  engineId: string;
168  name: string;
169  collapsed: boolean;
170  tracks: string[];  // Child track ids.
171}
172
173export interface EngineConfig {
174  id: string;
175  mode?: EngineMode;  // Is undefined until |ready| is true.
176  ready: boolean;
177  failed?: string;  // If defined the engine has crashed with the given message.
178  source: TraceSource;
179}
180
181export interface QueryConfig {
182  id: string;
183  engineId: string;
184  query: string;
185}
186
187export interface PermalinkConfig {
188  requestId?: string;  // Set by the frontend to request a new permalink.
189  hash?: string;       // Set by the controller when the link has been created.
190  isRecordingConfig?:
191      boolean;  // this permalink request is for a recording config only
192}
193
194export interface TraceTime {
195  startSec: number;
196  endSec: number;
197}
198
199export interface FrontendLocalState {
200  omniboxState: OmniboxState;
201  visibleState: VisibleState;
202}
203
204export interface Status {
205  msg: string;
206  timestamp: number;  // Epoch in seconds (Date.now() / 1000).
207}
208
209export interface Note {
210  noteType: 'DEFAULT';
211  id: string;
212  timestamp: number;
213  color: string;
214  text: string;
215}
216
217export interface AreaNote {
218  noteType: 'AREA';
219  id: string;
220  areaId: string;
221  color: string;
222  text: string;
223}
224
225export interface NoteSelection {
226  kind: 'NOTE';
227  id: string;
228}
229
230export interface SliceSelection {
231  kind: 'SLICE';
232  id: number;
233}
234
235export interface CounterSelection {
236  kind: 'COUNTER';
237  leftTs: number;
238  rightTs: number;
239  id: number;
240}
241
242export interface HeapProfileSelection {
243  kind: 'HEAP_PROFILE';
244  id: number;
245  upid: number;
246  ts: number;
247  type: string;
248}
249
250export interface PerfSamplesSelection {
251  kind: 'PERF_SAMPLES';
252  id: number;
253  upid: number;
254  ts: number;
255  type: string;
256}
257
258export interface FlamegraphState {
259  kind: 'FLAMEGRAPH_STATE';
260  upids: number[];
261  startNs: number;
262  endNs: number;
263  type: string;
264  viewingOption: FlamegraphStateViewingOption;
265  focusRegex: string;
266  expandedCallsite?: CallsiteInfo;
267}
268
269export interface CpuProfileSampleSelection {
270  kind: 'CPU_PROFILE_SAMPLE';
271  id: number;
272  utid: number;
273  ts: number;
274}
275
276export interface ChromeSliceSelection {
277  kind: 'CHROME_SLICE';
278  id: number;
279  table: string;
280}
281
282export interface ThreadStateSelection {
283  kind: 'THREAD_STATE';
284  id: number;
285}
286
287type Selection =
288    (NoteSelection|SliceSelection|CounterSelection|HeapProfileSelection|
289     CpuProfileSampleSelection|ChromeSliceSelection|ThreadStateSelection|
290     AreaSelection|PerfSamplesSelection)&{trackId?: string};
291export type SelectionKind = Selection['kind'];  // 'THREAD_STATE' | 'SLICE' ...
292
293export interface LogsPagination {
294  offset: number;
295  count: number;
296}
297
298export interface RecordingTarget {
299  name: string;
300  os: TargetOs;
301}
302
303export interface AdbRecordingTarget extends RecordingTarget {
304  serial: string;
305}
306
307export interface Sorting {
308  column: string;
309  direction: 'DESC'|'ASC';
310}
311
312export interface AggregationState {
313  id: string;
314  sorting?: Sorting;
315}
316
317export interface MetricsState {
318  availableMetrics?: string[];  // Undefined until list is loaded.
319  selectedIndex?: number;
320  requestedMetric?: string;  // Unset after metric request is handled.
321}
322
323export interface PivotTableConfig {
324  availableColumns?: TableAttrs[];   // Undefined until list is loaded.
325  availableAggregations?: string[];  // Undefined until list is loaded.
326}
327
328export interface PivotTableState {
329  id: string;
330  name: string;
331  selectedPivots: PivotAttrs[];
332  selectedAggregations: AggregationAttrs[];
333  requestedAction?:  // Unset after pivot table column request is handled.
334      {action: string, attrs?: SubQueryAttrs};
335  isLoadingQuery: boolean;
336  traceTime?: TraceTime;
337  selectedTrackIds?: number[];
338}
339
340// Auxiliary metadata needed to parse the query result, as well as to render it
341// correctly. Generated together with the text of query and passed without the
342// change to the query response.
343export interface PivotTableReduxQueryMetadata {
344  tableName: string;
345  pivotColumns: string[];
346  aggregationColumns: string[];
347}
348
349// Everything that's necessary to run the query for pivot table
350export interface PivotTableReduxQuery {
351  text: string;
352  metadata: PivotTableReduxQueryMetadata;
353}
354
355// Pivot table query result
356export interface PivotTableReduxResult {
357  // Hierarchical pivot structure on top of rows
358  tree: PivotTree;
359  // Copy of the query metadata from the request, bundled up with the query
360  // result to ensure the correct rendering.
361  metadata: PivotTableReduxQueryMetadata;
362}
363
364export interface PivotTableReduxState {
365  // Currently selected area, if null, pivot table is not going to be visible.
366  selectionArea: Area|null;
367  // Increasing identifier of the query request, used to avoid performing the
368  // same query more than once.
369  queryId: number;
370  // Query request
371  query: PivotTableReduxQuery|null;
372  // Query response
373  queryResult: PivotTableReduxResult|null;
374}
375
376export interface LoadedConfigNone {
377  type: 'NONE';
378}
379
380export interface LoadedConfigAutomatic {
381  type: 'AUTOMATIC';
382}
383
384export interface LoadedConfigNamed {
385  type: 'NAMED';
386  name: string;
387}
388
389export type LoadedConfig =
390    LoadedConfigNone|LoadedConfigAutomatic|LoadedConfigNamed;
391
392export interface State {
393  // tslint:disable-next-line:no-any
394  [key: string]: any;
395  version: number;
396  nextId: number;
397  nextNoteId: number;
398  nextAreaId: number;
399
400  /**
401   * State of the ConfigEditor.
402   */
403  recordConfig: RecordConfig;
404  displayConfigAsPbtxt: boolean;
405  lastLoadedConfig: LoadedConfig;
406
407  /**
408   * Open traces.
409   */
410  newEngineMode: NewEngineMode;
411  engines: ObjectById<EngineConfig>;
412  traceTime: TraceTime;
413  traceUuid?: string;
414  trackGroups: ObjectById<TrackGroupState>;
415  tracks: ObjectById<TrackState>;
416  uiTrackIdByTraceTrackId: {[key: number]: string;};
417  areas: ObjectById<AreaById>;
418  aggregatePreferences: ObjectById<AggregationState>;
419  visibleTracks: string[];
420  scrollingTracks: string[];
421  pinnedTracks: string[];
422  debugTrackId?: string;
423  lastTrackReloadRequest?: number;
424  queries: ObjectById<QueryConfig>;
425  metrics: MetricsState;
426  permalink: PermalinkConfig;
427  notes: ObjectById<Note|AreaNote>;
428  status: Status;
429  currentSelection: Selection|null;
430  currentFlamegraphState: FlamegraphState|null;
431  logsPagination: LogsPagination;
432  traceConversionInProgress: boolean;
433  pivotTableConfig: PivotTableConfig;
434  pivotTable: ObjectById<PivotTableState>;
435  pivotTableRedux: PivotTableReduxState;
436
437  /**
438   * This state is updated on the frontend at 60Hz and eventually syncronised to
439   * the controller at 10Hz. When the controller sends state updates to the
440   * frontend the frontend has special logic to pick whichever version of this
441   * key is most up to date.
442   */
443  frontendLocalState: FrontendLocalState;
444
445  // Show track perf debugging overlay
446  perfDebug: boolean;
447
448  // Show the sidebar extended
449  sidebarVisible: boolean;
450
451  // Hovered and focused events
452  hoveredUtid: number;
453  hoveredPid: number;
454  hoveredLogsTimestamp: number;
455  hoveredNoteTimestamp: number;
456  highlightedSliceId: number;
457  focusedFlowIdLeft: number;
458  focusedFlowIdRight: number;
459  pendingScrollId?: number;
460
461  searchIndex: number;
462  currentTab?: string;
463
464  /**
465   * Trace recording
466   */
467  recordingInProgress: boolean;
468  recordingCancelled: boolean;
469  extensionInstalled: boolean;
470  flamegraphModalDismissed: boolean;
471  recordingTarget: RecordingTarget;
472  availableAdbDevices: AdbRecordingTarget[];
473  lastRecordingError?: string;
474  recordingStatus?: string;
475
476  fetchChromeCategories: boolean;
477  chromeCategories: string[]|undefined;
478  analyzePageQuery?: string;
479}
480
481export const defaultTraceTime = {
482  startSec: 0,
483  endSec: 10,
484};
485
486export declare type RecordMode =
487    'STOP_WHEN_FULL' | 'RING_BUFFER' | 'LONG_TRACE';
488
489// 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome.
490export declare type TargetOs = 'S' | 'R' | 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
491
492export function isTargetOsAtLeast(target: RecordingTarget, osVersion: string) {
493  assertTrue(osVersion.length === 1);
494  return target.os >= osVersion;
495}
496
497export function isAndroidP(target: RecordingTarget) {
498  return target.os === 'P';
499}
500
501export function isAndroidTarget(target: RecordingTarget) {
502  return ['Q', 'P', 'O'].includes(target.os);
503}
504
505export function isChromeTarget(target: RecordingTarget) {
506  return ['C', 'CrOS'].includes(target.os);
507}
508
509export function isCrOSTarget(target: RecordingTarget) {
510  return target.os === 'CrOS';
511}
512
513export function isLinuxTarget(target: RecordingTarget) {
514  return target.os === 'L';
515}
516
517export function isAdbTarget(target: RecordingTarget):
518    target is AdbRecordingTarget {
519  return !!(target as AdbRecordingTarget).serial;
520}
521
522export function hasActiveProbes(config: RecordConfig) {
523  const fieldsWithEmptyResult =
524      new Set<string>(['hpBlockClient', 'allAtraceApps']);
525  let key: keyof RecordConfig;
526  for (key in config) {
527    if (typeof (config[key]) === 'boolean' && config[key] === true &&
528        !fieldsWithEmptyResult.has(key)) {
529      return true;
530    }
531  }
532  if (config.chromeCategoriesSelected.length > 0) {
533    return true;
534  }
535  return config.chromeHighOverheadCategoriesSelected.length > 0;
536}
537
538export function getDefaultRecordingTargets(): RecordingTarget[] {
539  return [
540    {os: 'Q', name: 'Android Q+'},
541    {os: 'P', name: 'Android P'},
542    {os: 'O', name: 'Android O-'},
543    {os: 'C', name: 'Chrome'},
544    {os: 'CrOS', name: 'Chrome OS (system trace)'},
545    {os: 'L', name: 'Linux desktop'}
546  ];
547}
548
549export function getBuiltinChromeCategoryList(): string[] {
550  // List of static Chrome categories, last updated at 2021-09-09 from HEAD of
551  // Chromium's //base/trace_event/builtin_categories.h.
552  return [
553    'accessibility',
554    'AccountFetcherService',
555    'android_webview',
556    'aogh',
557    'audio',
558    'base',
559    'benchmark',
560    'blink',
561    'blink.animations',
562    'blink.bindings',
563    'blink.console',
564    'blink.net',
565    'blink.resource',
566    'blink.user_timing',
567    'blink.worker',
568    'blink_gc',
569    'blink_style',
570    'Blob',
571    'browser',
572    'browsing_data',
573    'CacheStorage',
574    'Calculators',
575    'CameraStream',
576    'camera',
577    'cast_app',
578    'cast_perf_test',
579    'cast.mdns',
580    'cast.mdns.socket',
581    'cast.stream',
582    'cc',
583    'cc.debug',
584    'cdp.perf',
585    'chromeos',
586    'cma',
587    'compositor',
588    'content',
589    'content_capture',
590    'device',
591    'devtools',
592    'devtools.contrast',
593    'devtools.timeline',
594    'disk_cache',
595    'download',
596    'download_service',
597    'drm',
598    'drmcursor',
599    'dwrite',
600    'DXVA_Decoding',
601    'evdev',
602    'event',
603    'exo',
604    'extensions',
605    'explore_sites',
606    'FileSystem',
607    'file_system_provider',
608    'fonts',
609    'GAMEPAD',
610    'gpu',
611    'gpu.angle',
612    'gpu.capture',
613    'headless',
614    'hwoverlays',
615    'identity',
616    'ime',
617    'IndexedDB',
618    'input',
619    'io',
620    'ipc',
621    'Java',
622    'jni',
623    'jpeg',
624    'latency',
625    'latencyInfo',
626    'leveldb',
627    'loading',
628    'log',
629    'login',
630    'media',
631    'media_router',
632    'memory',
633    'midi',
634    'mojom',
635    'mus',
636    'native',
637    'navigation',
638    'net',
639    'netlog',
640    'offline_pages',
641    'omnibox',
642    'oobe',
643    'ozone',
644    'partition_alloc',
645    'passwords',
646    'p2p',
647    'page-serialization',
648    'paint_preview',
649    'pepper',
650    'PlatformMalloc',
651    'power',
652    'ppapi',
653    'ppapi_proxy',
654    'print',
655    'rail',
656    'renderer',
657    'renderer_host',
658    'renderer.scheduler',
659    'RLZ',
660    'safe_browsing',
661    'screenlock_monitor',
662    'segmentation_platform',
663    'sequence_manager',
664    'service_manager',
665    'ServiceWorker',
666    'sharing',
667    'shell',
668    'shortcut_viewer',
669    'shutdown',
670    'SiteEngagement',
671    'skia',
672    'sql',
673    'stadia_media',
674    'stadia_rtc',
675    'startup',
676    'sync',
677    'system_apps',
678    'test_gpu',
679    'thread_pool',
680    'toplevel',
681    'toplevel.flow',
682    'ui',
683    'v8',
684    'v8.execute',
685    'v8.wasm',
686    'ValueStoreFrontend::Backend',
687    'views',
688    'views.frame',
689    'viz',
690    'vk',
691    'wayland',
692    'webaudio',
693    'weblayer',
694    'WebCore',
695    'webrtc',
696    'xr',
697    'disabled-by-default-animation-worklet',
698    'disabled-by-default-audio',
699    'disabled-by-default-audio-worklet',
700    'disabled-by-default-base',
701    'disabled-by-default-blink.debug',
702    'disabled-by-default-blink.debug.display_lock',
703    'disabled-by-default-blink.debug.layout',
704    'disabled-by-default-blink.debug.layout.trees',
705    'disabled-by-default-blink.feature_usage',
706    'disabled-by-default-blink_gc',
707    'disabled-by-default-blink.image_decoding',
708    'disabled-by-default-blink.invalidation',
709    'disabled-by-default-cc',
710    'disabled-by-default-cc.debug',
711    'disabled-by-default-cc.debug.cdp-perf',
712    'disabled-by-default-cc.debug.display_items',
713    'disabled-by-default-cc.debug.picture',
714    'disabled-by-default-cc.debug.scheduler',
715    'disabled-by-default-cc.debug.scheduler.frames',
716    'disabled-by-default-cc.debug.scheduler.now',
717    'disabled-by-default-content.verbose',
718    'disabled-by-default-cpu_profiler',
719    'disabled-by-default-cpu_profiler.debug',
720    'disabled-by-default-devtools.screenshot',
721    'disabled-by-default-devtools.timeline',
722    'disabled-by-default-devtools.timeline.frame',
723    'disabled-by-default-devtools.timeline.inputs',
724    'disabled-by-default-devtools.timeline.invalidationTracking',
725    'disabled-by-default-devtools.timeline.layers',
726    'disabled-by-default-devtools.timeline.picture',
727    'disabled-by-default-file',
728    'disabled-by-default-fonts',
729    'disabled-by-default-gpu_cmd_queue',
730    'disabled-by-default-gpu.dawn',
731    'disabled-by-default-gpu.debug',
732    'disabled-by-default-gpu.decoder',
733    'disabled-by-default-gpu.device',
734    'disabled-by-default-gpu.service',
735    'disabled-by-default-gpu.vulkan.vma',
736    'disabled-by-default-histogram_samples',
737    'disabled-by-default-java-heap-profiler',
738    'disabled-by-default-layer-element',
739    'disabled-by-default-layout_shift.debug',
740    'disabled-by-default-lifecycles',
741    'disabled-by-default-loading',
742    'disabled-by-default-mediastream',
743    'disabled-by-default-memory-infra',
744    'disabled-by-default-memory-infra.v8.code_stats',
745    'disabled-by-default-mojom',
746    'disabled-by-default-net',
747    'disabled-by-default-network',
748    'disabled-by-default-paint-worklet',
749    'disabled-by-default-power',
750    'disabled-by-default-renderer.scheduler',
751    'disabled-by-default-renderer.scheduler.debug',
752    'disabled-by-default-sandbox',
753    'disabled-by-default-sequence_manager',
754    'disabled-by-default-sequence_manager.debug',
755    'disabled-by-default-sequence_manager.verbose_snapshots',
756    'disabled-by-default-skia',
757    'disabled-by-default-skia.gpu',
758    'disabled-by-default-skia.gpu.cache',
759    'disabled-by-default-skia.shaders',
760    'disabled-by-default-SyncFileSystem',
761    'disabled-by-default-system_stats',
762    'disabled-by-default-thread_pool_diagnostics',
763    'disabled-by-default-toplevel.ipc',
764    'disabled-by-default-user_action_samples',
765    'disabled-by-default-v8.compile',
766    'disabled-by-default-v8.cpu_profiler',
767    'disabled-by-default-v8.gc',
768    'disabled-by-default-v8.gc_stats',
769    'disabled-by-default-v8.ic_stats',
770    'disabled-by-default-v8.runtime',
771    'disabled-by-default-v8.runtime_stats',
772    'disabled-by-default-v8.runtime_stats_sampling',
773    'disabled-by-default-v8.stack_trace',
774    'disabled-by-default-v8.turbofan',
775    'disabled-by-default-v8.wasm.detailed',
776    'disabled-by-default-v8.wasm.turbofan',
777    'disabled-by-default-video_and_image_capture',
778    'disabled-by-default-viz.gpu_composite_time',
779    'disabled-by-default-viz.debug.overlay_planes',
780    'disabled-by-default-viz.hit_testing_flow',
781    'disabled-by-default-viz.overdraw',
782    'disabled-by-default-viz.quads',
783    'disabled-by-default-viz.surface_id_flow',
784    'disabled-by-default-viz.surface_lifetime',
785    'disabled-by-default-viz.triangles',
786    'disabled-by-default-webaudio.audionode',
787    'disabled-by-default-webrtc',
788    'disabled-by-default-worker.scheduler',
789    'disabled-by-default-xr.debug',
790  ];
791}
792
793export function getContainingTrackId(state: State, trackId: string): null|
794    string {
795  const track = state.tracks[trackId];
796  if (!track) {
797    return null;
798  }
799  const parentId = track.trackGroup;
800  if (!parentId) {
801    return null;
802  }
803  return parentId;
804}
805