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
15/**
16 * A plain js object, holding objects of type |Class| keyed by string id.
17 * We use this instead of using |Map| object since it is simpler and faster to
18 * serialize for use in postMessage.
19 */
20export interface ObjectById<Class extends{id: string}> { [id: string]: Class; }
21
22export type Timestamped<T> = {
23  [P in keyof T]: T[P];
24}&{lastUpdate: number};
25
26export type OmniboxState =
27    Timestamped<{omnibox: string; mode: 'SEARCH' | 'COMMAND'}>;
28
29export type VisibleState =
30    Timestamped<{startSec: number; endSec: number; resolution: number;}>;
31
32export interface AreaSelection {
33  kind: 'AREA';
34  areaId: string;
35  // When an area is marked it will be assigned a unique note id and saved as
36  // an AreaNote for the user to return to later. id = 0 is the special id that
37  // is overwritten when a new area is marked. Any other id is a persistent
38  // marking that will not be overwritten.
39  // When not set, the area selection will be replaced with any
40  // new area selection (i.e. not saved anywhere).
41  noteId?: string;
42}
43
44export type AreaById = Area&{id: string};
45
46export interface Area {
47  startSec: number;
48  endSec: number;
49  tracks: string[];
50}
51
52export const MAX_TIME = 180;
53
54// 3: TrackKindPriority and related sorting changes.
55export const STATE_VERSION = 3;
56
57export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
58
59export type EngineMode = 'WASM'|'HTTP_RPC';
60
61export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE'|'FORCE_BUILTIN_WASM';
62
63export enum TrackKindPriority {
64  'MAIN_THREAD' = 0,
65  'RENDER_THREAD' = 1,
66  'GPU_COMPLETION' = 2,
67  'ORDINARY' = 3
68}
69
70export type HeapProfileFlamegraphViewingOption =
71    'SPACE'|'ALLOC_SPACE'|'OBJECTS'|'ALLOC_OBJECTS';
72
73export interface CallsiteInfo {
74  id: number;
75  parentId: number;
76  depth: number;
77  name?: string;
78  totalSize: number;
79  selfSize: number;
80  mapping: string;
81  merged: boolean;
82  highlighted: boolean;
83}
84
85export interface TraceFileSource {
86  type: 'FILE';
87  file: File;
88}
89
90export interface TraceArrayBufferSource {
91  type: 'ARRAY_BUFFER';
92  title: string;
93  url?: string;
94  fileName?: string;
95  buffer: ArrayBuffer;
96}
97
98export interface TraceUrlSource {
99  type: 'URL';
100  url: string;
101}
102
103export interface TraceHttpRpcSource {
104  type: 'HTTP_RPC';
105}
106
107export type TraceSource =
108    TraceFileSource|TraceArrayBufferSource|TraceUrlSource|TraceHttpRpcSource;
109
110export interface TrackState {
111  id: string;
112  engineId: string;
113  kind: string;
114  name: string;
115  trackKindPriority: TrackKindPriority;
116  trackGroup?: string;
117  config: {};
118}
119
120export interface TrackGroupState {
121  id: string;
122  engineId: string;
123  name: string;
124  collapsed: boolean;
125  tracks: string[];  // Child track ids.
126}
127
128export interface EngineConfig {
129  id: string;
130  mode?: EngineMode;  // Is undefined until |ready| is true.
131  ready: boolean;
132  failed?: string;  // If defined the engine has crashed with the given message.
133  source: TraceSource;
134}
135
136export interface QueryConfig {
137  id: string;
138  engineId: string;
139  query: string;
140}
141
142export interface PermalinkConfig {
143  requestId?: string;  // Set by the frontend to request a new permalink.
144  hash?: string;       // Set by the controller when the link has been created.
145  isRecordingConfig?:
146      boolean;  // this permalink request is for a recording config only
147}
148
149export interface TraceTime {
150  startSec: number;
151  endSec: number;
152}
153
154export interface FrontendLocalState {
155  omniboxState: OmniboxState;
156  visibleState: VisibleState;
157}
158
159export interface Status {
160  msg: string;
161  timestamp: number;  // Epoch in seconds (Date.now() / 1000).
162}
163
164export interface Note {
165  noteType: 'DEFAULT'|'MOVIE';
166  id: string;
167  timestamp: number;
168  color: string;
169  text: string;
170}
171
172export interface AreaNote {
173  noteType: 'AREA';
174  id: string;
175  areaId: string;
176  color: string;
177  text: string;
178}
179
180export interface NoteSelection {
181  kind: 'NOTE';
182  id: string;
183}
184
185export interface SliceSelection {
186  kind: 'SLICE';
187  id: number;
188}
189
190export interface CounterSelection {
191  kind: 'COUNTER';
192  leftTs: number;
193  rightTs: number;
194  id: number;
195}
196
197export interface HeapProfileSelection {
198  kind: 'HEAP_PROFILE';
199  id: number;
200  upid: number;
201  ts: number;
202  type: string;
203}
204
205export interface HeapProfileFlamegraph {
206  kind: 'HEAP_PROFILE_FLAMEGRAPH';
207  id: number;
208  upid: number;
209  ts: number;
210  type: string;
211  viewingOption: HeapProfileFlamegraphViewingOption;
212  focusRegex: string;
213  expandedCallsite?: CallsiteInfo;
214}
215
216export interface CpuProfileSampleSelection {
217  kind: 'CPU_PROFILE_SAMPLE';
218  id: number;
219  utid: number;
220  ts: number;
221}
222
223export interface ChromeSliceSelection {
224  kind: 'CHROME_SLICE';
225  id: number;
226  table: string;
227}
228
229export interface ThreadStateSelection {
230  kind: 'THREAD_STATE';
231  id: number;
232}
233
234type Selection =
235    (NoteSelection|SliceSelection|CounterSelection|HeapProfileSelection|
236     CpuProfileSampleSelection|ChromeSliceSelection|ThreadStateSelection|
237     AreaSelection)&{trackId?: string};
238
239export interface LogsPagination {
240  offset: number;
241  count: number;
242}
243
244export interface RecordingTarget {
245  name: string;
246  os: TargetOs;
247}
248
249export interface AdbRecordingTarget extends RecordingTarget {
250  serial: string;
251}
252
253export interface Sorting {
254  column: string;
255  direction: 'DESC'|'ASC';
256}
257
258export interface AggregationState {
259  id: string;
260  sorting?: Sorting;
261}
262
263export interface MetricsState {
264  availableMetrics?: string[];  // Undefined until list is loaded.
265  selectedIndex?: number;
266  requestedMetric?: string;  // Unset after metric request is handled.
267}
268
269export interface State {
270  // tslint:disable-next-line:no-any
271  [key: string]: any;
272  version: number;
273  route: string|null;
274  nextId: number;
275  nextNoteId: number;
276  nextAreaId: number;
277
278  /**
279   * State of the ConfigEditor.
280   */
281  recordConfig: RecordConfig;
282  displayConfigAsPbtxt: boolean;
283
284  /**
285   * Open traces.
286   */
287  newEngineMode: NewEngineMode;
288  engines: ObjectById<EngineConfig>;
289  traceTime: TraceTime;
290  trackGroups: ObjectById<TrackGroupState>;
291  tracks: ObjectById<TrackState>;
292  areas: ObjectById<AreaById>;
293  aggregatePreferences: ObjectById<AggregationState>;
294  visibleTracks: string[];
295  scrollingTracks: string[];
296  pinnedTracks: string[];
297  debugTrackId?: string;
298  lastTrackReloadRequest?: number;
299  queries: ObjectById<QueryConfig>;
300  metrics: MetricsState;
301  permalink: PermalinkConfig;
302  notes: ObjectById<Note|AreaNote>;
303  status: Status;
304  currentSelection: Selection|null;
305  currentHeapProfileFlamegraph: HeapProfileFlamegraph|null;
306  logsPagination: LogsPagination;
307  traceConversionInProgress: boolean;
308
309  /**
310   * This state is updated on the frontend at 60Hz and eventually syncronised to
311   * the controller at 10Hz. When the controller sends state updates to the
312   * frontend the frontend has special logic to pick whichever version of this
313   * key is most up to date.
314   */
315  frontendLocalState: FrontendLocalState;
316
317  video: string | null;
318  videoEnabled: boolean;
319  videoOffset: number;
320  videoNoteIds: string[];
321  scrubbingEnabled: boolean;
322  flagPauseEnabled: boolean;
323
324  /**
325   * Trace recording
326   */
327  recordingInProgress: boolean;
328  recordingCancelled: boolean;
329  extensionInstalled: boolean;
330  recordingTarget: RecordingTarget;
331  availableAdbDevices: AdbRecordingTarget[];
332  lastRecordingError?: string;
333  recordingStatus?: string;
334
335  updateChromeCategories: boolean;
336  chromeCategories: string[]|undefined;
337  analyzePageQuery?: string;
338}
339
340export const defaultTraceTime = {
341  startSec: 0,
342  endSec: 10,
343};
344
345export declare type RecordMode =
346    'STOP_WHEN_FULL' | 'RING_BUFFER' | 'LONG_TRACE';
347
348// 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome.
349export declare type TargetOs = 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
350
351export function isAndroidP(target: RecordingTarget) {
352  return target.os === 'P';
353}
354
355export function isAndroidTarget(target: RecordingTarget) {
356  return ['Q', 'P', 'O'].includes(target.os);
357}
358
359export function isChromeTarget(target: RecordingTarget) {
360  return ['C', 'CrOS'].includes(target.os);
361}
362
363export function isCrOSTarget(target: RecordingTarget) {
364  return target.os === 'CrOS';
365}
366
367export function isLinuxTarget(target: RecordingTarget) {
368  return target.os === 'L';
369}
370
371export function isAdbTarget(target: RecordingTarget):
372    target is AdbRecordingTarget {
373  if ((target as AdbRecordingTarget).serial) return true;
374  return false;
375}
376
377export function hasActiveProbes(config: RecordConfig) {
378  const fieldsWithEmptyResult = new Set<string>(['hpBlockClient']);
379  for (const key in config) {
380    if (typeof (config[key]) === 'boolean' && config[key] === true &&
381        !fieldsWithEmptyResult.has(key)) {
382      return true;
383    }
384  }
385  return false;
386}
387
388export interface RecordConfig {
389  [key: string]: null|number|boolean|string|string[];
390
391  // Global settings
392  mode: RecordMode;
393  durationMs: number;
394  bufferSizeMb: number;
395  maxFileSizeMb: number;      // Only for mode == 'LONG_TRACE'.
396  fileWritePeriodMs: number;  // Only for mode == 'LONG_TRACE'.
397
398  cpuSched: boolean;
399  cpuFreq: boolean;
400  cpuCoarse: boolean;
401  cpuCoarsePollMs: number;
402  cpuSyscall: boolean;
403
404  screenRecord: boolean;
405
406  gpuFreq: boolean;
407  gpuMemTotal: boolean;
408
409  ftrace: boolean;
410  atrace: boolean;
411  ftraceEvents: string[];
412  ftraceExtraEvents: string;
413  atraceCats: string[];
414  atraceApps: string;
415  ftraceBufferSizeKb: number;
416  ftraceDrainPeriodMs: number;
417  androidLogs: boolean;
418  androidLogBuffers: string[];
419  androidFrameTimeline: boolean;
420
421  batteryDrain: boolean;
422  batteryDrainPollMs: number;
423
424  boardSensors: boolean;
425
426  memHiFreq: boolean;
427  memLmk: boolean;
428  meminfo: boolean;
429  meminfoPeriodMs: number;
430  meminfoCounters: string[];
431  vmstat: boolean;
432  vmstatPeriodMs: number;
433  vmstatCounters: string[];
434
435  heapProfiling: boolean;
436  hpSamplingIntervalBytes: number;
437  hpProcesses: string;
438  hpContinuousDumpsPhase: number;
439  hpContinuousDumpsInterval: number;
440  hpSharedMemoryBuffer: number;
441  hpBlockClient: boolean;
442  hpAllHeaps: boolean;
443
444  javaHeapDump: boolean;
445  jpProcesses: string;
446  jpContinuousDumpsPhase: number;
447  jpContinuousDumpsInterval: number;
448
449  procStats: boolean;
450  procStatsPeriodMs: number;
451
452  chromeCategoriesSelected: string[];
453}
454
455export function createEmptyRecordConfig(): RecordConfig {
456  return {
457    mode: 'STOP_WHEN_FULL',
458    durationMs: 10000.0,
459    maxFileSizeMb: 100,
460    fileWritePeriodMs: 2500,
461    bufferSizeMb: 64.0,
462
463    cpuSched: false,
464    cpuFreq: false,
465    cpuSyscall: false,
466
467    screenRecord: false,
468
469    gpuFreq: false,
470    gpuMemTotal: false,
471
472    ftrace: false,
473    atrace: false,
474    ftraceEvents: [],
475    ftraceExtraEvents: '',
476    atraceCats: [],
477    atraceApps: '',
478    ftraceBufferSizeKb: 2 * 1024,
479    ftraceDrainPeriodMs: 250,
480    androidLogs: false,
481    androidLogBuffers: [],
482    androidFrameTimeline: false,
483
484    cpuCoarse: false,
485    cpuCoarsePollMs: 1000,
486
487    batteryDrain: false,
488    batteryDrainPollMs: 1000,
489
490    boardSensors: false,
491
492    memHiFreq: false,
493    meminfo: false,
494    meminfoPeriodMs: 1000,
495    meminfoCounters: [],
496
497    vmstat: false,
498    vmstatPeriodMs: 1000,
499    vmstatCounters: [],
500
501    heapProfiling: false,
502    hpSamplingIntervalBytes: 4096,
503    hpProcesses: '',
504    hpContinuousDumpsPhase: 0,
505    hpContinuousDumpsInterval: 0,
506    hpSharedMemoryBuffer: 8 * 1048576,
507    hpBlockClient: true,
508    hpAllHeaps: false,
509
510    javaHeapDump: false,
511    jpProcesses: '',
512    jpContinuousDumpsPhase: 0,
513    jpContinuousDumpsInterval: 0,
514
515    memLmk: false,
516    procStats: false,
517    procStatsPeriodMs: 1000,
518
519    chromeCategoriesSelected: [],
520  };
521}
522
523export function getDefaultRecordingTargets(): RecordingTarget[] {
524  return [
525    {os: 'Q', name: 'Android Q+'},
526    {os: 'P', name: 'Android P'},
527    {os: 'O', name: 'Android O-'},
528    {os: 'C', name: 'Chrome'},
529    {os: 'CrOS', name: 'Chrome OS (system trace)'},
530    {os: 'L', name: 'Linux desktop'}
531  ];
532}
533
534export function getBuiltinChromeCategoryList(): string[] {
535  // List of static Chrome categories, last updated at Chromium 81.0.4021.0 from
536  // Chromium's //base/trace_event/builtin_categories.h.
537  return [
538    'accessibility',
539    'AccountFetcherService',
540    'android_webview',
541    'aogh',
542    'audio',
543    'base',
544    'benchmark',
545    'blink',
546    'blink.animations',
547    'blink.bindings',
548    'blink.console',
549    'blink_gc',
550    'blink.net',
551    'blink_style',
552    'blink.user_timing',
553    'blink.worker',
554    'Blob',
555    'browser',
556    'browsing_data',
557    'CacheStorage',
558    'Calculators',
559    'CameraStream',
560    'camera',
561    'cast_app',
562    'cast_perf_test',
563    'cast.mdns',
564    'cast.mdns.socket',
565    'cast.stream',
566    'cc',
567    'cc.debug',
568    'cdp.perf',
569    'chromeos',
570    'cma',
571    'compositor',
572    'content',
573    'content_capture',
574    'device',
575    'devtools',
576    'devtools.contrast',
577    'devtools.timeline',
578    'disk_cache',
579    'download',
580    'download_service',
581    'drm',
582    'drmcursor',
583    'dwrite',
584    'DXVA_Decoding',
585    'EarlyJava',
586    'evdev',
587    'event',
588    'exo',
589    'explore_sites',
590    'FileSystem',
591    'file_system_provider',
592    'fonts',
593    'GAMEPAD',
594    'gpu',
595    'gpu.angle',
596    'gpu.capture',
597    'headless',
598    'hwoverlays',
599    'identity',
600    'ime',
601    'IndexedDB',
602    'input',
603    'io',
604    'ipc',
605    'Java',
606    'jni',
607    'jpeg',
608    'latency',
609    'latencyInfo',
610    'leveldb',
611    'loading',
612    'log',
613    'login',
614    'media',
615    'media_router',
616    'memory',
617    'midi',
618    'mojom',
619    'mus',
620    'native',
621    'navigation',
622    'net',
623    'netlog',
624    'offline_pages',
625    'omnibox',
626    'oobe',
627    'ozone',
628    'partition_alloc',
629    'passwords',
630    'p2p',
631    'page-serialization',
632    'paint_preview',
633    'pepper',
634    'PlatformMalloc',
635    'power',
636    'ppapi',
637    'ppapi_proxy',
638    'print',
639    'rail',
640    'renderer',
641    'renderer_host',
642    'renderer.scheduler',
643    'RLZ',
644    'safe_browsing',
645    'screenlock_monitor',
646    'sequence_manager',
647    'service_manager',
648    'ServiceWorker',
649    'sharing',
650    'shell',
651    'shortcut_viewer',
652    'shutdown',
653    'SiteEngagement',
654    'skia',
655    'sql',
656    'stadia_media',
657    'stadia_rtc',
658    'startup',
659    'sync',
660    'sync_lock_contention',
661    'test_gpu',
662    'thread_pool',
663    'toplevel',
664    'toplevel.flow',
665    'ui',
666    'v8',
667    'v8.execute',
668    'v8.wasm',
669    'ValueStoreFrontend::Backend',
670    'views',
671    'views.frame',
672    'viz',
673    'vk',
674    'wayland',
675    'webaudio',
676    'weblayer',
677    'WebCore',
678    'webrtc',
679    'xr',
680    'disabled-by-default-animation-worklet',
681    'disabled-by-default-audio',
682    'disabled-by-default-audio-worklet',
683    'disabled-by-default-blink.debug',
684    'disabled-by-default-blink.debug.display_lock',
685    'disabled-by-default-blink.debug.layout',
686    'disabled-by-default-blink.debug.layout.trees',
687    'disabled-by-default-blink.feature_usage',
688    'disabled-by-default-blink_gc',
689    'disabled-by-default-blink.image_decoding',
690    'disabled-by-default-blink.invalidation',
691    'disabled-by-default-cc',
692    'disabled-by-default-cc.debug',
693    'disabled-by-default-cc.debug.cdp-perf',
694    'disabled-by-default-cc.debug.display_items',
695    'disabled-by-default-cc.debug.picture',
696    'disabled-by-default-cc.debug.scheduler',
697    'disabled-by-default-cc.debug.scheduler.frames',
698    'disabled-by-default-cc.debug.scheduler.now',
699    'disabled-by-default-content.verbose',
700    'disabled-by-default-cpu_profiler',
701    'disabled-by-default-cpu_profiler.debug',
702    'disabled-by-default-devtools.screenshot',
703    'disabled-by-default-devtools.timeline',
704    'disabled-by-default-devtools.timeline.frame',
705    'disabled-by-default-devtools.timeline.inputs',
706    'disabled-by-default-devtools.timeline.invalidationTracking',
707    'disabled-by-default-devtools.timeline.layers',
708    'disabled-by-default-devtools.timeline.picture',
709    'disabled-by-default-file',
710    'disabled-by-default-fonts',
711    'disabled-by-default-gpu_cmd_queue',
712    'disabled-by-default-gpu.dawn',
713    'disabled-by-default-gpu.debug',
714    'disabled-by-default-gpu.decoder',
715    'disabled-by-default-gpu.device',
716    'disabled-by-default-gpu.service',
717    'disabled-by-default-gpu.vulkan.vma',
718    'disabled-by-default-histogram_samples',
719    'disabled-by-default-ipc.flow',
720    'disabled-by-default-java-heap-profiler',
721    'disabled-by-default-layer-element',
722    'disabled-by-default-layout_shift.debug',
723    'disabled-by-default-lifecycles',
724    'disabled-by-default-loading',
725    'disabled-by-default-mediastream',
726    'disabled-by-default-memory-infra',
727    'disabled-by-default-memory-infra.v8.code_stats',
728    'disabled-by-default-mojom',
729    'disabled-by-default-net',
730    'disabled-by-default-network',
731    'disabled-by-default-paint-worklet',
732    'disabled-by-default-power',
733    'disabled-by-default-renderer.scheduler',
734    'disabled-by-default-renderer.scheduler.debug',
735    'disabled-by-default-sandbox',
736    'disabled-by-default-sequence_manager',
737    'disabled-by-default-sequence_manager.debug',
738    'disabled-by-default-sequence_manager.verbose_snapshots',
739    'disabled-by-default-skia',
740    'disabled-by-default-skia.gpu',
741    'disabled-by-default-skia.gpu.cache',
742    'disabled-by-default-skia.shaders',
743    'disabled-by-default-SyncFileSystem',
744    'disabled-by-default-system_stats',
745    'disabled-by-default-thread_pool_diagnostics',
746    'disabled-by-default-toplevel.flow',
747    'disabled-by-default-toplevel.ipc',
748    'disabled-by-default-user_action_samples',
749    'disabled-by-default-v8.compile',
750    'disabled-by-default-v8.cpu_profiler',
751    'disabled-by-default-v8.cpu_profiler.hires',
752    'disabled-by-default-v8.gc',
753    'disabled-by-default-v8.gc_stats',
754    'disabled-by-default-v8.ic_stats',
755    'disabled-by-default-v8.runtime',
756    'disabled-by-default-v8.runtime_stats',
757    'disabled-by-default-v8.runtime_stats_sampling',
758    'disabled-by-default-v8.stack_trace',
759    'disabled-by-default-v8.turbofan',
760    'disabled-by-default-v8.wasm',
761    'disabled-by-default-v8.wasm.detailed',
762    'disabled-by-default-v8.wasm.turbofan',
763    'disabled-by-default-video_and_image_capture',
764    'disabled-by-default-viz.debug.overlay_planes',
765    'disabled-by-default-viz.gpu_composite_time',
766    'disabled-by-default-viz.hit_testing_flow',
767    'disabled-by-default-viz.overdraw',
768    'disabled-by-default-viz.quads',
769    'disabled-by-default-viz.surface_id_flow',
770    'disabled-by-default-viz.surface_lifetime',
771    'disabled-by-default-viz.triangles',
772    'disabled-by-default-webaudio.audionode',
773    'disabled-by-default-webrtc',
774    'disabled-by-default-worker.scheduler',
775    'disabled-by-default-xr.debug',
776  ];
777}
778
779export function createEmptyState(): State {
780  return {
781    version: STATE_VERSION,
782    route: null,
783    nextId: 0,
784    nextNoteId: 1,  // 0 is reserved for ephemeral area marking.
785    nextAreaId: 0,
786    newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE',
787    engines: {},
788    traceTime: {...defaultTraceTime},
789    tracks: {},
790    aggregatePreferences: {},
791    trackGroups: {},
792    visibleTracks: [],
793    pinnedTracks: [],
794    scrollingTracks: [],
795    areas: {},
796    queries: {},
797    metrics: {},
798    permalink: {},
799    notes: {},
800
801    recordConfig: createEmptyRecordConfig(),
802    displayConfigAsPbtxt: false,
803
804    frontendLocalState: {
805      omniboxState: {
806        lastUpdate: 0,
807        omnibox: '',
808        mode: 'SEARCH',
809      },
810
811      visibleState: {
812        ...defaultTraceTime,
813        lastUpdate: 0,
814        resolution: 0,
815      },
816    },
817
818    logsPagination: {
819      offset: 0,
820      count: 0,
821    },
822
823    status: {msg: '', timestamp: 0},
824    currentSelection: null,
825    currentHeapProfileFlamegraph: null,
826    traceConversionInProgress: false,
827
828    video: null,
829    videoEnabled: false,
830    videoOffset: 0,
831    videoNoteIds: [],
832    scrubbingEnabled: false,
833    flagPauseEnabled: false,
834    recordingInProgress: false,
835    recordingCancelled: false,
836    extensionInstalled: false,
837    recordingTarget: getDefaultRecordingTargets()[0],
838    availableAdbDevices: [],
839
840    updateChromeCategories: false,
841    chromeCategories: undefined,
842  };
843}
844
845export function getContainingTrackId(state: State, trackId: string): null|
846    string {
847  const track = state.tracks[trackId];
848  if (!track) {
849    return null;
850  }
851  const parentId = track.trackGroup;
852  if (!parentId) {
853    return null;
854  }
855  return parentId;
856}
857