Merge "ui: Remove logic to port chrome scroll jank plugin flag to plugin flag" into main
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 50b81bc..131332d 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -15322,6 +15322,8 @@
 
     THREAD_MEMORY_INFRA = 50;
     THREAD_SAMPLING_PROFILER = 51;
+
+    THREAD_COMPOSITOR_GPU = 52;
   };
 
   optional ThreadType thread_type = 1;
diff --git a/protos/perfetto/trace/track_event/chrome_thread_descriptor.proto b/protos/perfetto/trace/track_event/chrome_thread_descriptor.proto
index bd82b5a..5e2f119 100644
--- a/protos/perfetto/trace/track_event/chrome_thread_descriptor.proto
+++ b/protos/perfetto/trace/track_event/chrome_thread_descriptor.proto
@@ -77,6 +77,8 @@
 
     THREAD_MEMORY_INFRA = 50;
     THREAD_SAMPLING_PROFILER = 51;
+
+    THREAD_COMPOSITOR_GPU = 52;
   };
 
   optional ThreadType thread_type = 1;
diff --git a/src/trace_processor/importers/proto/chrome_string_lookup.cc b/src/trace_processor/importers/proto/chrome_string_lookup.cc
index f3e1125..f688510 100644
--- a/src/trace_processor/importers/proto/chrome_string_lookup.cc
+++ b/src/trace_processor/importers/proto/chrome_string_lookup.cc
@@ -170,6 +170,7 @@
      "NetworkConfigWatcher"},
     {ChromeThreadDescriptor::THREAD_WASAPI_RENDER, "wasapi_render_thread"},
     {ChromeThreadDescriptor::THREAD_LOADER_LOCK_SAMPLER, "LoaderLockSampler"},
+    {ChromeThreadDescriptor::THREAD_COMPOSITOR_GPU, "CompositorGpuThread"},
 };
 
 }  // namespace
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index 4791e8e..caba9f0 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -60,6 +60,7 @@
   'dev.perfetto.RestorePinnedTrack',
   'dev.perfetto.Sched',
   'dev.perfetto.Screenshots',
+  'dev.perfetto.SqlModules',
   'dev.perfetto.Thread',
   'dev.perfetto.ThreadState',
   'dev.perfetto.TimelineSync',
diff --git a/ui/src/core/feature_flags.ts b/ui/src/core/feature_flags.ts
index b88f93b..7fa974d 100644
--- a/ui/src/core/feature_flags.ts
+++ b/ui/src/core/feature_flags.ts
@@ -16,6 +16,7 @@
 // ~everywhere and the are "statically" initialized (i.e. files construct Flags
 // at import time) if this file starts importing anything we will quickly run
 // into issues with initialization order which will be a pain.
+import {z} from 'zod';
 import {Flag, FlagSettings, OverrideState} from '../public/feature_flag';
 
 export interface FlagStore {
@@ -28,22 +29,6 @@
   [id: string]: OverrideState;
 }
 
-// Check if the given object is a valid FlagOverrides.
-// This is necessary since someone could modify the persisted flags
-// behind our backs.
-function isFlagOverrides(o: object): o is FlagOverrides {
-  const states = [
-    OverrideState.TRUE.toString(),
-    OverrideState.FALSE.toString(),
-  ];
-  for (const v of Object.values(o)) {
-    if (typeof v !== 'string' || !states.includes(v)) {
-      return false;
-    }
-  }
-  return true;
-}
-
 class Flags {
   private store: FlagStore;
   private flags: Map<string, FlagImpl>;
@@ -89,8 +74,17 @@
 
   load(): void {
     const o = this.store.load();
-    if (isFlagOverrides(o)) {
-      this.overrides = o;
+
+    // Check if the given object is a valid FlagOverrides.
+    // This is necessary since someone could modify the persisted flags
+    // behind our backs.
+    const flagsSchema = z.record(
+      z.string(),
+      z.union([z.literal(OverrideState.TRUE), z.literal(OverrideState.FALSE)]),
+    );
+    const {success, data} = flagsSchema.safeParse(o);
+    if (success) {
+      this.overrides = data;
     }
   }
 
diff --git a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
index 33b42b4..03ab372 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
@@ -13,7 +13,6 @@
 // limitations under the License.
 
 import {NamedRow} from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
 import {Slice} from '../../public/track';
 import {
   CustomSqlTableDefConfig,
@@ -22,15 +21,17 @@
 import {JANK_COLOR} from './jank_colors';
 import {TrackEventSelection} from '../../public/selection';
 import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
+import {Trace} from '../../public/trace';
 
 export const JANKY_LATENCY_NAME = 'Janky EventLatency';
 
 export class EventLatencyTrack extends CustomSqlTableSliceTrack {
   constructor(
-    args: NewTrackArgs,
+    trace: Trace,
+    uri: string,
     private baseTable: string,
   ) {
-    super(args);
+    super(trace, uri);
   }
 
   getSqlSource(): string {
diff --git a/ui/src/core_plugins/chrome_scroll_jank/index.ts b/ui/src/core_plugins/chrome_scroll_jank/index.ts
index a4ecca8..04feac4 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/index.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/index.ts
@@ -54,10 +54,7 @@
     ctx.tracks.registerTrack({
       uri,
       title,
-      track: new TopLevelScrollTrack({
-        trace: ctx,
-        uri,
-      }),
+      track: new TopLevelScrollTrack(ctx, uri),
     });
 
     const track = new TrackNode({uri, title});
@@ -166,7 +163,7 @@
     ctx.tracks.registerTrack({
       uri,
       title,
-      track: new EventLatencyTrack({trace: ctx, uri}, baseTable),
+      track: new EventLatencyTrack(ctx, uri, baseTable),
     });
 
     const track = new TrackNode({uri, title});
@@ -187,10 +184,7 @@
     ctx.tracks.registerTrack({
       uri,
       title,
-      track: new ScrollJankV3Track({
-        trace: ctx,
-        uri,
-      }),
+      track: new ScrollJankV3Track(ctx, uri),
     });
 
     const track = new TrackNode({uri, title});
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index b5d57fa..2bea9d9 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -25,7 +25,6 @@
 import {MenuDivider, MenuItem, PopupMenu2} from '../widgets/menu';
 import {LONG, NUM} from '../trace_processor/query_result';
 import {checkerboardExcept} from './checkerboard';
-import {NewTrackArgs} from './track';
 import {AsyncDisposableStack} from '../base/disposable_stack';
 import {Trace} from '../public/trace';
 
@@ -180,13 +179,7 @@
   unit?: string;
 }
 
-export type BaseCounterTrackArgs = NewTrackArgs & {
-  options?: Partial<CounterOptions>;
-};
-
 export abstract class BaseCounterTrack implements Track {
-  protected trace: Trace;
-  protected uri: string;
   protected trackUuid = uuidv4Sql();
 
   // This is the over-skirted cached bounds:
@@ -204,7 +197,6 @@
 
   private mousePos = {x: 0, y: 0};
   private hover?: CounterTooltipState;
-  private defaultOptions: Partial<CounterOptions>;
   private options?: CounterOptions;
 
   private readonly trash: AsyncDisposableStack;
@@ -244,10 +236,11 @@
     };
   }
 
-  constructor(args: BaseCounterTrackArgs) {
-    this.trace = args.trace;
-    this.uri = args.uri;
-    this.defaultOptions = args.options ?? {};
+  constructor(
+    protected readonly trace: Trace,
+    protected readonly uri: string,
+    protected readonly defaultOptions: Partial<CounterOptions> = {},
+  ) {
     this.trash = new AsyncDisposableStack();
   }
 
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index 0c18970..83c9d73 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -28,7 +28,6 @@
 import {LONG, NUM} from '../trace_processor/query_result';
 import {checkerboardExcept} from './checkerboard';
 import {DEFAULT_SLICE_LAYOUT, SliceLayout} from './slice_layout';
-import {NewTrackArgs} from './track';
 import {BUCKETS_PER_PIXEL, CacheKey} from '../core/timeline_cache';
 import {uuidv4Sql} from '../base/uuid';
 import {AsyncDisposableStack} from '../base/disposable_stack';
@@ -163,8 +162,6 @@
 > implements Track
 {
   protected sliceLayout: SliceLayout = {...DEFAULT_SLICE_LAYOUT};
-  protected trace: Trace;
-  protected uri: string;
   protected trackUuid = uuidv4Sql();
 
   // This is the over-skirted cached bounds:
@@ -240,9 +237,10 @@
     _selectedSlice?: SliceT,
   ): void {}
 
-  constructor(args: NewTrackArgs) {
-    this.trace = args.trace;
-    this.uri = args.uri;
+  constructor(
+    protected readonly trace: Trace,
+    protected readonly uri: string,
+  ) {
     // Work out the extra columns.
     // This is the union of the embedder-defined columns and the base columns
     // we know about (ts, dur, ...).
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index 7a23285..07043e9 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -26,11 +26,11 @@
   SLICE_FLAGS_INSTANT,
 } from './base_slice_track';
 import {ThreadSliceDetailsPanel} from './thread_slice_details_tab';
-import {NewTrackArgs} from './track';
 import {renderDuration} from './widgets/duration';
 import {TraceImpl} from '../core/trace_impl';
 import {assertIsInstance} from '../base/logging';
 import {SourceDataset, Dataset} from '../trace_processor/dataset';
+import {Trace} from '../public/trace';
 
 export const NAMED_ROW = {
   // Base columns (tsq, ts, dur, id, depth).
@@ -45,8 +45,8 @@
   SliceType extends Slice = Slice,
   RowType extends NamedRow = NamedRow,
 > extends BaseSliceTrack<SliceType, RowType> {
-  constructor(args: NewTrackArgs) {
-    super(args);
+  constructor(trace: Trace, uri: string) {
+    super(trace, uri);
   }
 
   // Converts a SQL result row to an "Impl" Slice.
diff --git a/ui/src/frontend/thread_slice_track.ts b/ui/src/frontend/thread_slice_track.ts
index 6e47c8a..10a829f 100644
--- a/ui/src/frontend/thread_slice_track.ts
+++ b/ui/src/frontend/thread_slice_track.ts
@@ -16,13 +16,13 @@
 import {clamp} from '../base/math_utils';
 import {NAMED_ROW, NamedSliceTrack} from './named_slice_track';
 import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from './slice_layout';
-import {NewTrackArgs} from './track';
 import {LONG_NULL} from '../trace_processor/query_result';
 import {Slice} from '../public/track';
 import {TrackEventDetails} from '../public/selection';
 import {ThreadSliceDetailsPanel} from './thread_slice_details_tab';
 import {TraceImpl} from '../core/trace_impl';
 import {assertIsInstance} from '../base/logging';
+import {Trace} from '../public/trace';
 
 export const THREAD_SLICE_ROW = {
   // Base columns (tsq, ts, dur, id, depth).
@@ -35,12 +35,13 @@
 
 export class ThreadSliceTrack extends NamedSliceTrack<Slice, ThreadSliceRow> {
   constructor(
-    args: NewTrackArgs,
-    private trackId: number,
+    trace: Trace,
+    uri: string,
+    private readonly trackId: number,
     maxDepth: number,
-    private tableName: string = 'slice',
+    private readonly tableName: string = 'slice',
   ) {
-    super(args);
+    super(trace, uri);
     this.sliceLayout = {
       ...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS,
       depthGuess: maxDepth,
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
deleted file mode 100644
index f10e21e..0000000
--- a/ui/src/frontend/track.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {Trace} from '../public/trace';
-
-export interface NewTrackArgs {
-  uri: string;
-  trace: Trace;
-}
diff --git a/ui/src/frontend/tracks/custom_sql_table_slice_track.ts b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
index 7d3caa3..c157267 100644
--- a/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
+++ b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
@@ -14,11 +14,11 @@
 
 import {generateSqlWithInternalLayout} from '../../trace_processor/sql_utils/layout';
 import {NAMED_ROW, NamedRow, NamedSliceTrack} from '../named_slice_track';
-import {NewTrackArgs} from '../track';
 import {createView} from '../../trace_processor/sql_utils';
 import {Slice} from '../../public/track';
 import {AsyncDisposableStack} from '../../base/disposable_stack';
 import {sqlNameSafe} from '../../base/string_utils';
+import {Trace} from '../../public/trace';
 
 export interface CustomSqlImportConfig {
   modules: string[];
@@ -39,9 +39,9 @@
 > {
   protected readonly tableName;
 
-  constructor(args: NewTrackArgs) {
-    super(args);
-    this.tableName = `customsqltableslicetrack_${sqlNameSafe(args.uri)}`;
+  constructor(trace: Trace, uri: string) {
+    super(trace, uri);
+    this.tableName = `customsqltableslicetrack_${sqlNameSafe(uri)}`;
   }
 
   getRowSpec(): NamedRow {
diff --git a/ui/src/frontend/visualized_args_track.ts b/ui/src/frontend/visualized_args_track.ts
index fdea5b3..4b52b9d 100644
--- a/ui/src/frontend/visualized_args_track.ts
+++ b/ui/src/frontend/visualized_args_track.ts
@@ -46,7 +46,7 @@
     const escapedArgName = argName.replace(/[^a-zA-Z]/g, '_');
     const viewName = `__arg_visualisation_helper_${escapedArgName}_${uuid}_slice`;
 
-    super({trace, uri}, trackId, maxDepth, viewName);
+    super(trace, uri, trackId, maxDepth, viewName);
     this.viewName = viewName;
     this.argName = argName;
     this.onClose = onClose;
diff --git a/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts
index 0486e6e..0d55ff1 100644
--- a/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts
+++ b/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts
@@ -16,8 +16,8 @@
 import {clamp} from '../../base/math_utils';
 import {NAMED_ROW, NamedSliceTrack} from '../../frontend/named_slice_track';
 import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from '../../frontend/slice_layout';
-import {NewTrackArgs} from '../../frontend/track';
 import {TrackEventDetails} from '../../public/selection';
+import {Trace} from '../../public/trace';
 import {Slice} from '../../public/track';
 import {SourceDataset, Dataset} from '../../trace_processor/dataset';
 import {
@@ -39,11 +39,12 @@
 
 export class AsyncSliceTrack extends NamedSliceTrack<Slice, ThreadSliceRow> {
   constructor(
-    args: NewTrackArgs,
+    trace: Trace,
+    uri: string,
     maxDepth: number,
     private readonly trackIds: number[],
   ) {
-    super(args);
+    super(trace, uri);
     this.sliceLayout = {
       ...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS,
       depthGuess: maxDepth,
diff --git a/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts
index 9b0c6e7..ecd8dab 100644
--- a/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts
+++ b/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts
@@ -197,7 +197,7 @@
             kind: SLICE_TRACK_KIND,
             scope: 'global',
           },
-          track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
+          track: new AsyncSliceTrack(ctx, uri, maxDepth, trackIds),
         });
         const trackNode = new TrackNode({
           uri,
@@ -283,7 +283,7 @@
           scope: 'process',
           upid,
         },
-        track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
+        track: new AsyncSliceTrack(ctx, uri, maxDepth, trackIds),
       });
       const track = new TrackNode({uri, title, sortOrder: 30});
       trackIds.forEach((id) => {
@@ -384,7 +384,7 @@
         chips: removeFalsyValues([
           isKernelThread === 0 && isMainThread === 1 && 'main thread',
         ]),
-        track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
+        track: new AsyncSliceTrack(ctx, uri, maxDepth, trackIds),
       });
       const track = new TrackNode({uri, title, sortOrder: 20});
       trackIds.forEach((id) => {
diff --git a/ui/src/plugins/dev.perfetto.Counter/index.ts b/ui/src/plugins/dev.perfetto.Counter/index.ts
index 074e2c4..ef063ca 100644
--- a/ui/src/plugins/dev.perfetto.Counter/index.ts
+++ b/ui/src/plugins/dev.perfetto.Counter/index.ts
@@ -85,10 +85,6 @@
   //   options.yRangeSharingKey = 'mem';
   // }
 
-  if (name.startsWith('battery_stats.')) {
-    options.yRangeSharingKey = 'battery_stats';
-  }
-
   // All 'Entity residency: foo bar1234' tracks should share a y-axis
   // with 'Entity residency: foo baz5678' etc tracks:
   {
@@ -163,16 +159,16 @@
           kind: COUNTER_TRACK_KIND,
           trackIds: [trackId],
         },
-        track: new TraceProcessorCounterTrack({
-          trace: ctx,
+        track: new TraceProcessorCounterTrack(
+          ctx,
           uri,
-          trackId,
-          trackName: title,
-          options: {
+          {
             ...getDefaultCounterOptions(title),
             unit,
           },
-        }),
+          trackId,
+          title,
+        ),
       });
       const track = new TrackNode({uri, title});
       ctx.workspace.addChildInOrder(track);
@@ -242,13 +238,13 @@
           trackIds: [trackId],
           scope,
         },
-        track: new TraceProcessorCounterTrack({
-          trace: ctx,
+        track: new TraceProcessorCounterTrack(
+          ctx,
           uri,
-          trackId: trackId,
-          trackName: name,
-          options: getDefaultCounterOptions(name),
-        }),
+          getDefaultCounterOptions(name),
+          trackId,
+          name,
+        ),
       });
       const trackNode = new TrackNode({uri, title: name, sortOrder: -20});
       ctx.workspace.addChildInOrder(trackNode);
@@ -309,13 +305,13 @@
           upid: upid ?? undefined,
           scope: 'thread',
         },
-        track: new TraceProcessorCounterTrack({
-          trace: ctx,
+        track: new TraceProcessorCounterTrack(
+          ctx,
           uri,
-          trackId: trackId,
-          trackName: name,
-          options: getDefaultCounterOptions(name),
-        }),
+          getDefaultCounterOptions(name),
+          trackId,
+          name,
+        ),
       });
       const group = getOrCreateGroupForThread(ctx.workspace, utid);
       const track = new TrackNode({uri, title: name, sortOrder: 30});
@@ -367,13 +363,13 @@
           upid,
           scope: 'process',
         },
-        track: new TraceProcessorCounterTrack({
-          trace: ctx,
+        track: new TraceProcessorCounterTrack(
+          ctx,
           uri,
-          trackId: trackId,
-          trackName: name,
-          options: getDefaultCounterOptions(name),
-        }),
+          getDefaultCounterOptions(name),
+          trackId,
+          name,
+        ),
       });
       const group = getOrCreateGroupForProcess(ctx.workspace, upid);
       const track = new TrackNode({uri, title: name, sortOrder: 20});
@@ -402,13 +398,13 @@
           trackIds: [it.id],
           scope: 'gpuFreq',
         },
-        track: new TraceProcessorCounterTrack({
-          trace: ctx,
+        track: new TraceProcessorCounterTrack(
+          ctx,
           uri,
-          trackId: it.id,
-          trackName: name,
-          options: getDefaultCounterOptions(name),
-        }),
+          getDefaultCounterOptions(name),
+          it.id,
+          name,
+        ),
       });
       const track = new TrackNode({uri, title: name, sortOrder: -20});
       ctx.workspace.addChildInOrder(track);
diff --git a/ui/src/plugins/dev.perfetto.Counter/trace_processor_counter_track.ts b/ui/src/plugins/dev.perfetto.Counter/trace_processor_counter_track.ts
index ddbdbcb..a76fcef 100644
--- a/ui/src/plugins/dev.perfetto.Counter/trace_processor_counter_track.ts
+++ b/ui/src/plugins/dev.perfetto.Counter/trace_processor_counter_track.ts
@@ -12,33 +12,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {LONG, LONG_NULL, NUM} from '../../trace_processor/query_result';
+import {Time} from '../../base/time';
 import {
   BaseCounterTrack,
-  BaseCounterTrackArgs,
+  CounterOptions,
 } from '../../frontend/base_counter_track';
-
-import {TrackMouseEvent} from '../../public/track';
 import {TrackEventDetails} from '../../public/selection';
-import {Time} from '../../base/time';
+import {Trace} from '../../public/trace';
+import {TrackMouseEvent} from '../../public/track';
+import {LONG, LONG_NULL, NUM} from '../../trace_processor/query_result';
 import {CounterDetailsPanel} from './counter_details_panel';
 
-interface TraceProcessorCounterTrackArgs extends BaseCounterTrackArgs {
-  readonly trackId: number;
-  readonly trackName: string;
-  readonly rootTable?: string;
-}
-
 export class TraceProcessorCounterTrack extends BaseCounterTrack {
-  private readonly trackId: number;
-  private readonly rootTable: string;
-  private readonly trackName: string;
-
-  constructor(args: TraceProcessorCounterTrackArgs) {
-    super(args);
-    this.trackId = args.trackId;
-    this.rootTable = args.rootTable ?? 'counter';
-    this.trackName = args.trackName;
+  constructor(
+    trace: Trace,
+    uri: string,
+    options: Partial<CounterOptions>,
+    private readonly trackId: number,
+    private readonly trackName: string,
+    private readonly rootTable: string = 'counter',
+  ) {
+    super(trace, uri, options);
   }
 
   getSqlSource() {
diff --git a/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_track.ts b/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_track.ts
index 4baeddf..0bd6fe8 100644
--- a/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_track.ts
+++ b/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_track.ts
@@ -20,10 +20,10 @@
   OnSliceClickArgs,
 } from '../../frontend/base_slice_track';
 import {NAMED_ROW, NamedRow} from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
 import {NUM} from '../../trace_processor/query_result';
 import {Slice} from '../../public/track';
 import {CpuProfileSampleFlamegraphDetailsPanel} from './cpu_profile_details_panel';
+import {Trace} from '../../public/trace';
 
 interface CpuProfileRow extends NamedRow {
   callsiteId: number;
@@ -31,10 +31,11 @@
 
 export class CpuProfileTrack extends BaseSliceTrack<Slice, CpuProfileRow> {
   constructor(
-    args: NewTrackArgs,
+    trace: Trace,
+    uri: string,
     private utid: number,
   ) {
-    super(args);
+    super(trace, uri);
   }
 
   protected getRowSpec(): CpuProfileRow {
diff --git a/ui/src/plugins/dev.perfetto.CpuProfile/index.ts b/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
index 31e49fe..e33e341 100644
--- a/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
@@ -60,13 +60,7 @@
           utid,
           ...(exists(upid) && {upid}),
         },
-        track: new CpuProfileTrack(
-          {
-            trace: ctx,
-            uri,
-          },
-          utid,
-        ),
+        track: new CpuProfileTrack(ctx, uri, utid),
       });
       const group = getOrCreateGroupForThread(ctx.workspace, utid);
       const track = new TrackNode({uri, title, sortOrder: -40});
diff --git a/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts b/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
index 2c19110..c6ef4b3 100644
--- a/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
@@ -55,7 +55,7 @@
     uri: string,
     private trackIds: number[],
   ) {
-    super({trace, uri});
+    super(trace, uri);
     this.sliceLayout = {
       ...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS,
       depthGuess: maxDepth,
diff --git a/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts b/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
index ff04311..27557fc 100644
--- a/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
@@ -33,7 +33,7 @@
     uri: string,
     private trackIds: number[],
   ) {
-    super({trace, uri});
+    super(trace, uri);
     this.sliceLayout = {
       ...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS,
       depthGuess: maxDepth,
diff --git a/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts b/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
index 9705156..b34717e 100644
--- a/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
+++ b/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
@@ -21,13 +21,14 @@
   NamedRow,
   NamedSliceTrack,
 } from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
 import {TrackNode} from '../../public/workspace';
 class GpuPidTrack extends NamedSliceTrack {
-  upid: number;
-
-  constructor(args: NewTrackArgs, upid: number) {
-    super(args);
+  constructor(
+    trace: Trace,
+    uri: string,
+    protected readonly upid: number,
+  ) {
+    super(trace, uri);
     this.upid = upid;
   }
 
@@ -84,7 +85,7 @@
       ctx.tracks.registerTrack({
         uri,
         title,
-        track: new GpuPidTrack({trace: ctx, uri}, upid),
+        track: new GpuPidTrack(ctx, uri, upid),
       });
       const track = new TrackNode({uri, title});
       track.uri = uri;
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_track.ts b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_track.ts
index d4effa4..45f1d0e 100644
--- a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_track.ts
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_track.ts
@@ -19,13 +19,13 @@
   OnSliceClickArgs,
   OnSliceOverArgs,
 } from '../../frontend/base_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
 import {
   ProfileType,
   profileType,
   TrackEventDetails,
   TrackEventSelection,
 } from '../../public/selection';
+import {Trace} from '../../public/trace';
 import {Slice} from '../../public/track';
 import {LONG, STR} from '../../trace_processor/query_result';
 import {HeapProfileFlamegraphDetailsPanel} from './heap_profile_details_panel';
@@ -44,12 +44,13 @@
   HeapProfileRow
 > {
   constructor(
-    args: NewTrackArgs,
+    trace: Trace,
+    uri: string,
     private readonly tableName: string,
     private readonly upid: number,
     private readonly heapProfileIsIncomplete: boolean,
   ) {
-    super(args);
+    super(trace, uri);
   }
 
   getSqlSource(): string {
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/index.ts b/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
index f4f9121..2e0591f 100644
--- a/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
@@ -92,15 +92,7 @@
           kind: HEAP_PROFILE_TRACK_KIND,
           upid,
         },
-        track: new HeapProfileTrack(
-          {
-            trace: ctx,
-            uri,
-          },
-          tableName,
-          upid,
-          incomplete,
-        ),
+        track: new HeapProfileTrack(ctx, uri, tableName, upid, incomplete),
       });
       const group = getOrCreateGroupForProcess(ctx.workspace, upid);
       const track = new TrackNode({uri, title, sortOrder: -30});
diff --git a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts
index 26ac305..04e8b13 100644
--- a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts
@@ -57,13 +57,7 @@
           kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
           upid,
         },
-        track: new ProcessPerfSamplesProfileTrack(
-          {
-            trace: ctx,
-            uri,
-          },
-          upid,
-        ),
+        track: new ProcessPerfSamplesProfileTrack(ctx, uri, upid),
       });
       const group = getOrCreateGroupForProcess(ctx.workspace, upid);
       const track = new TrackNode({uri, title, sortOrder: -40});
@@ -103,13 +97,7 @@
           utid,
           upid: upid ?? undefined,
         },
-        track: new ThreadPerfSamplesProfileTrack(
-          {
-            trace: ctx,
-            uri,
-          },
-          utid,
-        ),
+        track: new ThreadPerfSamplesProfileTrack(ctx, uri, utid),
       });
       const group = getOrCreateGroupForThread(ctx.workspace, utid);
       const track = new TrackNode({uri, title, sortOrder: -50});
diff --git a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts
index 839d0d1..06f3244 100644
--- a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts
+++ b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts
@@ -19,7 +19,6 @@
   BaseSliceTrack,
   OnSliceClickArgs,
 } from '../../frontend/base_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
 import {NAMED_ROW, NamedRow} from '../../frontend/named_slice_track';
 import {getColorForSample} from '../../public/lib/colorizer';
 import {
@@ -37,6 +36,7 @@
 import {time} from '../../base/time';
 import {TrackEventDetailsPanel} from '../../public/details_panel';
 import {Flamegraph, FLAMEGRAPH_STATE_SCHEMA} from '../../widgets/flamegraph';
+import {Trace} from '../../public/trace';
 
 interface PerfSampleRow extends NamedRow {
   callsiteId: number;
@@ -46,8 +46,8 @@
   Slice,
   PerfSampleRow
 > {
-  constructor(args: NewTrackArgs) {
-    super(args);
+  constructor(trace: Trace, uri: string) {
+    super(trace, uri);
   }
 
   protected getRowSpec(): PerfSampleRow {
@@ -75,10 +75,11 @@
 
 export class ProcessPerfSamplesProfileTrack extends BasePerfSamplesProfileTrack {
   constructor(
-    args: NewTrackArgs,
-    private upid: number,
+    trace: Trace,
+    uri: string,
+    private readonly upid: number,
   ) {
-    super(args);
+    super(trace, uri);
   }
 
   getSqlSource(): string {
@@ -170,10 +171,11 @@
 
 export class ThreadPerfSamplesProfileTrack extends BasePerfSamplesProfileTrack {
   constructor(
-    args: NewTrackArgs,
-    private utid: number,
+    trace: Trace,
+    uri: string,
+    private readonly utid: number,
   ) {
-    super(args);
+    super(trace, uri);
   }
 
   getSqlSource(): string {
diff --git a/ui/src/plugins/dev.perfetto.Sched/active_cpu_count.ts b/ui/src/plugins/dev.perfetto.Sched/active_cpu_count.ts
index 328d386..02ff0b2 100644
--- a/ui/src/plugins/dev.perfetto.Sched/active_cpu_count.ts
+++ b/ui/src/plugins/dev.perfetto.Sched/active_cpu_count.ts
@@ -33,10 +33,7 @@
   private readonly cpuType?: CPUType;
 
   constructor(ctx: TrackContext, trace: Trace, cpuType?: CPUType) {
-    super({
-      trace,
-      uri: ctx.trackUri,
-    });
+    super(trace, ctx.trackUri);
     this.cpuType = cpuType;
   }
 
diff --git a/ui/src/plugins/dev.perfetto.Sched/index.ts b/ui/src/plugins/dev.perfetto.Sched/index.ts
index 68d3d0b..8be599d 100644
--- a/ui/src/plugins/dev.perfetto.Sched/index.ts
+++ b/ui/src/plugins/dev.perfetto.Sched/index.ts
@@ -31,10 +31,7 @@
     ctx.tracks.registerTrack({
       uri: runnableThreadCountUri,
       title: 'Runnable thread count',
-      track: new RunnableThreadCountTrack({
-        trace: ctx,
-        uri: runnableThreadCountUri,
-      }),
+      track: new RunnableThreadCountTrack(ctx, runnableThreadCountUri),
     });
     ctx.commands.registerCommand({
       id: 'dev.perfetto.Sched.AddRunnableThreadCountTrackCommand',
@@ -47,10 +44,10 @@
     ctx.tracks.registerTrack({
       uri: uninterruptibleSleepThreadCountUri,
       title: 'Uninterruptible Sleep thread count',
-      track: new UninterruptibleSleepThreadCountTrack({
-        trace: ctx,
-        uri: uninterruptibleSleepThreadCountUri,
-      }),
+      track: new UninterruptibleSleepThreadCountTrack(
+        ctx,
+        uninterruptibleSleepThreadCountUri,
+      ),
     });
     ctx.commands.registerCommand({
       id: 'dev.perfetto.Sched.AddUninterruptibleSleepThreadCountTrackCommand',
diff --git a/ui/src/plugins/dev.perfetto.Sched/thread_count.ts b/ui/src/plugins/dev.perfetto.Sched/thread_count.ts
index 88fe892..73e7ee2 100644
--- a/ui/src/plugins/dev.perfetto.Sched/thread_count.ts
+++ b/ui/src/plugins/dev.perfetto.Sched/thread_count.ts
@@ -16,11 +16,11 @@
   BaseCounterTrack,
   CounterOptions,
 } from '../../frontend/base_counter_track';
-import {NewTrackArgs} from '../../frontend/track';
+import {Trace} from '../../public/trace';
 
 abstract class ThreadCountTrack extends BaseCounterTrack {
-  constructor(args: NewTrackArgs) {
-    super(args);
+  constructor(trace: Trace, uri: string) {
+    super(trace, uri);
   }
 
   protected getDefaultCounterOptions(): CounterOptions {
diff --git a/ui/src/plugins/dev.perfetto.Screenshots/index.ts b/ui/src/plugins/dev.perfetto.Screenshots/index.ts
index 68c7d64..7cadbf7 100644
--- a/ui/src/plugins/dev.perfetto.Screenshots/index.ts
+++ b/ui/src/plugins/dev.perfetto.Screenshots/index.ts
@@ -35,10 +35,7 @@
       ctx.tracks.registerTrack({
         uri,
         title,
-        track: new ScreenshotsTrack({
-          trace: ctx,
-          uri,
-        }),
+        track: new ScreenshotsTrack(ctx, uri),
         tags: {
           kind: ScreenshotsTrack.kind,
         },
diff --git a/ui/src/plugins/dev.perfetto.SqlModules/index.ts b/ui/src/plugins/dev.perfetto.SqlModules/index.ts
new file mode 100644
index 0000000..f2fde8d
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.SqlModules/index.ts
@@ -0,0 +1,35 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {assetSrc} from '../../base/assets';
+import {assertExists} from '../../base/logging';
+import {PerfettoPlugin} from '../../public/plugin';
+import {SqlModules} from './sql_modules';
+import {SQL_MODULES_DOCS_SCHEMA, SqlModulesImpl} from './sql_modules_impl';
+
+export default class implements PerfettoPlugin {
+  static readonly id = 'dev.perfetto.SqlModules';
+  private sqlModules?: SqlModules;
+
+  async onTraceLoad() {
+    const resp = await fetch(assetSrc('stdlib_docs.json'));
+    const json = await resp.json();
+    const docs = SQL_MODULES_DOCS_SCHEMA.parse(json);
+    this.sqlModules = new SqlModulesImpl(docs);
+  }
+
+  getSqlModules() {
+    return assertExists(this.sqlModules);
+  }
+}
diff --git a/ui/src/plugins/dev.perfetto.SqlModules/sql_modules.ts b/ui/src/plugins/dev.perfetto.SqlModules/sql_modules.ts
new file mode 100644
index 0000000..9f0503a
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.SqlModules/sql_modules.ts
@@ -0,0 +1,90 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Handles the access to all of the Perfetto SQL modules accessible to Trace
+//  Processor.
+export interface SqlModules {
+  // Returns names of all tables/views between all loaded Perfetto SQL modules.
+  listTables(): string[];
+
+  // Returns Perfetto SQL table/view if it was loaded in one of the Perfetto
+  // SQL module.
+  getTable(tableName: string): SqlTable | undefined;
+}
+
+// Handles the access to a specific Perfetto SQL Package. Package consists of
+// Perfetto SQL modules.
+export interface SqlPackage {
+  readonly name: string;
+  readonly modules: SqlModule[];
+  listTables(): string[];
+  getTable(tableName: string): SqlTable | undefined;
+}
+
+// Handles the access to a specific Perfetto SQL module.
+export interface SqlModule {
+  readonly includeKey: string;
+  readonly dataObjects: SqlTable[];
+  readonly functions: SqlFunction[];
+  readonly tableFunctions: SqlTableFunction[];
+  readonly macros: SqlMacro[];
+}
+
+// The definition of Perfetto SQL table/view.
+export interface SqlTable {
+  readonly name: string;
+  readonly description: string;
+  readonly type: string;
+  readonly columns: SqlColumn[];
+}
+
+// The definition of Perfetto SQL function.
+export interface SqlFunction {
+  readonly name: string;
+  readonly description: string;
+  readonly args: SqlArgument[];
+  readonly returnType: string;
+  readonly returnDesc: string;
+}
+
+// The definition of Perfetto SQL table function.
+export interface SqlTableFunction {
+  readonly name: string;
+  readonly description: string;
+  readonly args: SqlArgument[];
+  readonly returnCols: SqlColumn[];
+}
+
+// The definition of Perfetto SQL macro.
+export interface SqlMacro {
+  readonly name: string;
+  readonly description: string;
+  readonly args: SqlArgument[];
+  readonly returnType: string;
+}
+
+// The definition of Perfetto SQL column.
+export interface SqlColumn {
+  readonly name: string;
+  readonly description: string;
+  readonly type: string;
+}
+
+// The definition of Perfetto SQL argument. Can be used for functions, table
+// functions or macros.
+export interface SqlArgument {
+  readonly name: string;
+  readonly description: string;
+  readonly type: string;
+}
diff --git a/ui/src/plugins/dev.perfetto.SqlModules/sql_modules_impl.ts b/ui/src/plugins/dev.perfetto.SqlModules/sql_modules_impl.ts
new file mode 100644
index 0000000..ee6fe79
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.SqlModules/sql_modules_impl.ts
@@ -0,0 +1,254 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {z} from 'zod';
+import {
+  SqlModules,
+  SqlColumn,
+  SqlFunction,
+  SqlArgument,
+  SqlMacro,
+  SqlModule,
+  SqlPackage,
+  SqlTable,
+  SqlTableFunction,
+} from './sql_modules';
+
+export class SqlModulesImpl implements SqlModules {
+  readonly packages: SqlPackage[];
+
+  constructor(docs: SqlModulesDocsSchema) {
+    this.packages = docs.map((json) => new StdlibPackageImpl(json));
+  }
+
+  listTables(): string[] {
+    const tables: string[] = [];
+    for (const stdlibPackage of this.packages) {
+      tables.concat(stdlibPackage.listTables());
+    }
+    return tables;
+  }
+
+  getTable(tableName: string): SqlTable | undefined {
+    for (const stdlibPackage of this.packages) {
+      const maybeTable = stdlibPackage.getTable(tableName);
+      if (maybeTable) {
+        return maybeTable;
+      }
+    }
+    return undefined;
+  }
+}
+
+export class StdlibPackageImpl implements SqlPackage {
+  readonly name: string;
+  readonly modules: SqlModule[];
+
+  constructor(docs: DocsPackageSchemaType) {
+    this.name = docs.name;
+    this.modules = [];
+    for (const moduleJson of docs.modules) {
+      this.modules.push(new StdlibModuleImpl(moduleJson));
+    }
+  }
+
+  getTable(tableName: string): SqlTable | undefined {
+    for (const module of this.modules) {
+      for (const dataObj of module.dataObjects) {
+        if (dataObj.name == tableName) {
+          return dataObj;
+        }
+      }
+    }
+    return undefined;
+  }
+
+  listTables(): string[] {
+    return this.modules.flatMap((module) =>
+      module.dataObjects.map((dataObj) => dataObj.name),
+    );
+  }
+}
+
+export class StdlibModuleImpl implements SqlModule {
+  readonly includeKey: string;
+  readonly dataObjects: SqlTable[];
+  readonly functions: SqlFunction[];
+  readonly tableFunctions: SqlTableFunction[];
+  readonly macros: SqlMacro[];
+
+  constructor(docs: DocsModuleSchemaType) {
+    this.includeKey = docs.module_name;
+    this.dataObjects = docs.data_objects.map(
+      (json) => new StdlibDataObjectImpl(json),
+    );
+    this.functions = docs.functions.map((json) => new StdlibFunctionImpl(json));
+    this.tableFunctions = docs.table_functions.map(
+      (json) => new StdlibTableFunctionImpl(json),
+    );
+    this.macros = docs.macros.map((json) => new StdlibMacroImpl(json));
+  }
+}
+
+class StdlibMacroImpl implements SqlMacro {
+  readonly name: string;
+  readonly summaryDesc: string;
+  readonly description: string;
+  readonly args: SqlArgument[];
+  readonly returnType: string;
+
+  constructor(docs: DocsMacroSchemaType) {
+    this.name = docs.name;
+    this.summaryDesc = docs.summary_desc;
+    this.description = docs.desc;
+    this.returnType = docs.return_type;
+    this.args = [];
+    this.args = docs.args.map((json) => new StdlibFunctionArgImpl(json));
+  }
+}
+
+class StdlibTableFunctionImpl implements SqlTableFunction {
+  readonly name: string;
+  readonly summaryDesc: string;
+  readonly description: string;
+  readonly args: SqlArgument[];
+  readonly returnCols: SqlColumn[];
+
+  constructor(docs: DocsTableFunctionSchemaType) {
+    this.name = docs.name;
+    this.summaryDesc = docs.summary_desc;
+    this.description = docs.desc;
+    this.args = docs.args.map((json) => new StdlibFunctionArgImpl(json));
+    this.returnCols = docs.cols.map((json) => new StdlibColumnImpl(json));
+  }
+}
+
+class StdlibFunctionImpl implements SqlFunction {
+  readonly name: string;
+  readonly summaryDesc: string;
+  readonly description: string;
+  readonly args: SqlArgument[];
+  readonly returnType: string;
+  readonly returnDesc: string;
+
+  constructor(docs: DocsFunctionSchemaType) {
+    this.name = docs.name;
+    this.summaryDesc = docs.summary_desc;
+    this.description = docs.desc;
+    this.returnType = docs.return_type;
+    this.returnDesc = docs.return_desc;
+    this.args = docs.args.map((json) => new StdlibFunctionArgImpl(json));
+  }
+}
+
+class StdlibDataObjectImpl implements SqlTable {
+  name: string;
+  description: string;
+  type: string;
+  columns: SqlColumn[];
+
+  constructor(docs: DocsDataObjectSchemaType) {
+    this.name = docs.name;
+    this.description = docs.desc;
+    this.type = docs.type;
+    this.columns = docs.cols.map((json) => new StdlibColumnImpl(json));
+  }
+}
+
+class StdlibColumnImpl implements SqlColumn {
+  name: string;
+  type: string;
+  description: string;
+
+  constructor(docs: DocsArgOrColSchemaType) {
+    this.type = docs.type;
+    this.description = docs.desc;
+    this.name = docs.name;
+  }
+}
+
+class StdlibFunctionArgImpl implements SqlArgument {
+  name: string;
+  description: string;
+  type: string;
+
+  constructor(docs: DocsArgOrColSchemaType) {
+    this.type = docs.type;
+    this.description = docs.desc;
+    this.name = docs.name;
+  }
+}
+
+const ARG_OR_COL_SCHEMA = z.object({
+  name: z.string(),
+  type: z.string(),
+  desc: z.string(),
+});
+type DocsArgOrColSchemaType = z.infer<typeof ARG_OR_COL_SCHEMA>;
+
+const DATA_OBJECT_SCHEMA = z.object({
+  name: z.string(),
+  desc: z.string(),
+  summary_desc: z.string(),
+  type: z.string(),
+  cols: z.array(ARG_OR_COL_SCHEMA),
+});
+type DocsDataObjectSchemaType = z.infer<typeof DATA_OBJECT_SCHEMA>;
+
+const FUNCTION_SCHEMA = z.object({
+  name: z.string(),
+  desc: z.string(),
+  summary_desc: z.string(),
+  args: z.array(ARG_OR_COL_SCHEMA),
+  return_type: z.string(),
+  return_desc: z.string(),
+});
+type DocsFunctionSchemaType = z.infer<typeof FUNCTION_SCHEMA>;
+
+const TABLE_FUNCTION_SCHEMA = z.object({
+  name: z.string(),
+  desc: z.string(),
+  summary_desc: z.string(),
+  args: z.array(ARG_OR_COL_SCHEMA),
+  cols: z.array(ARG_OR_COL_SCHEMA),
+});
+type DocsTableFunctionSchemaType = z.infer<typeof TABLE_FUNCTION_SCHEMA>;
+
+const MACRO_SCHEMA = z.object({
+  name: z.string(),
+  desc: z.string(),
+  summary_desc: z.string(),
+  return_desc: z.string(),
+  return_type: z.string(),
+  args: z.array(ARG_OR_COL_SCHEMA),
+});
+type DocsMacroSchemaType = z.infer<typeof MACRO_SCHEMA>;
+
+const MODULE_SCHEMA = z.object({
+  module_name: z.string(),
+  data_objects: z.array(DATA_OBJECT_SCHEMA),
+  functions: z.array(FUNCTION_SCHEMA),
+  table_functions: z.array(TABLE_FUNCTION_SCHEMA),
+  macros: z.array(MACRO_SCHEMA),
+});
+type DocsModuleSchemaType = z.infer<typeof MODULE_SCHEMA>;
+
+const PACKAGE_SCHEMA = z.object({
+  name: z.string(),
+  modules: z.array(MODULE_SCHEMA),
+});
+type DocsPackageSchemaType = z.infer<typeof PACKAGE_SCHEMA>;
+
+export const SQL_MODULES_DOCS_SCHEMA = z.array(PACKAGE_SCHEMA);
+export type SqlModulesDocsSchema = z.infer<typeof SQL_MODULES_DOCS_SCHEMA>;
diff --git a/ui/src/plugins/dev.perfetto.ThreadState/index.ts b/ui/src/plugins/dev.perfetto.ThreadState/index.ts
index a106654..155b35f 100644
--- a/ui/src/plugins/dev.perfetto.ThreadState/index.ts
+++ b/ui/src/plugins/dev.perfetto.ThreadState/index.ts
@@ -84,13 +84,7 @@
         chips: removeFalsyValues([
           isKernelThread === 0 && isMainThread === 1 && 'main thread',
         ]),
-        track: new ThreadStateTrack(
-          {
-            trace: ctx,
-            uri,
-          },
-          utid,
-        ),
+        track: new ThreadStateTrack(ctx, uri, utid),
       });
 
       const group = getOrCreateGroupForThread(ctx.workspace, utid);
diff --git a/ui/src/plugins/dev.perfetto.ThreadState/thread_state_track.ts b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_track.ts
index 3935465..4fdb791 100644
--- a/ui/src/plugins/dev.perfetto.ThreadState/thread_state_track.ts
+++ b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_track.ts
@@ -22,12 +22,12 @@
   SLICE_LAYOUT_FLAT_DEFAULTS,
   SliceLayout,
 } from '../../frontend/slice_layout';
-import {NewTrackArgs} from '../../frontend/track';
 import {NUM_NULL, STR} from '../../trace_processor/query_result';
 import {Slice} from '../../public/track';
 import {translateState} from '../../trace_processor/sql_utils/thread_state';
 import {TrackEventDetails, TrackEventSelection} from '../../public/selection';
 import {ThreadStateDetailsPanel} from './thread_state_details_panel';
+import {Trace} from '../../public/trace';
 
 export const THREAD_STATE_ROW = {
   ...BASE_ROW,
@@ -41,10 +41,11 @@
   protected sliceLayout: SliceLayout = {...SLICE_LAYOUT_FLAT_DEFAULTS};
 
   constructor(
-    args: NewTrackArgs,
+    trace: Trace,
+    uri: string,
     private utid: number,
   ) {
-    super(args);
+    super(trace, uri);
   }
 
   // This is used by the base class to call iter().
diff --git a/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/index.ts b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/index.ts
index 6b96209..a2b7981 100644
--- a/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/index.ts
+++ b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/index.ts
@@ -39,10 +39,10 @@
         kind: CriticalUserInteractionTrack.kind,
       },
       title: 'Chrome Interactions',
-      track: new CriticalUserInteractionTrack({
-        trace: ctx,
-        uri: CriticalUserInteractionTrack.kind,
-      }),
+      track: new CriticalUserInteractionTrack(
+        ctx,
+        CriticalUserInteractionTrack.kind,
+      ),
     });
   }
 }
diff --git a/ui/src/plugins/org.chromium.ChromeTasks/track.ts b/ui/src/plugins/org.chromium.ChromeTasks/track.ts
index ff2f361..030af9e 100644
--- a/ui/src/plugins/org.chromium.ChromeTasks/track.ts
+++ b/ui/src/plugins/org.chromium.ChromeTasks/track.ts
@@ -25,9 +25,9 @@
   constructor(
     trace: Trace,
     uri: string,
-    private utid: Utid,
+    private readonly utid: Utid,
   ) {
-    super({trace, uri});
+    super(trace, uri);
   }
 
   getSqlDataSource(): CustomSqlTableDefConfig {
diff --git a/ui/src/plugins/org.kernel.LinuxKernelSubsystems/index.ts b/ui/src/plugins/org.kernel.LinuxKernelSubsystems/index.ts
index 0bca424..8d7ff66 100644
--- a/ui/src/plugins/org.kernel.LinuxKernelSubsystems/index.ts
+++ b/ui/src/plugins/org.kernel.LinuxKernelSubsystems/index.ts
@@ -66,14 +66,7 @@
       ctx.tracks.registerTrack({
         uri,
         title,
-        track: new AsyncSliceTrack(
-          {
-            trace: ctx,
-            uri,
-          },
-          0,
-          [trackId],
-        ),
+        track: new AsyncSliceTrack(ctx, uri, 0, [trackId]),
         tags: {
           kind: SLICE_TRACK_KIND,
           trackIds: [trackId],
diff --git a/ui/src/plugins/org.kernel.SuspendResumeLatency/index.ts b/ui/src/plugins/org.kernel.SuspendResumeLatency/index.ts
index 96b0ddb..973af15 100644
--- a/ui/src/plugins/org.kernel.SuspendResumeLatency/index.ts
+++ b/ui/src/plugins/org.kernel.SuspendResumeLatency/index.ts
@@ -14,7 +14,6 @@
 
 import {NUM, STR_NULL} from '../../trace_processor/query_result';
 import {AsyncSliceTrack} from '../dev.perfetto.AsyncSlices/async_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
 import {PerfettoPlugin} from '../../public/plugin';
 import {Trace} from '../../public/trace';
 import {TrackNode} from '../../public/workspace';
@@ -31,12 +30,13 @@
 // TODO(stevegolton): Remove this?
 class SuspendResumeSliceTrack extends AsyncSliceTrack {
   constructor(
-    args: NewTrackArgs,
+    trace: Trace,
+    uri: string,
     maxDepth: number,
     trackIds: number[],
     private readonly threads: ThreadMap,
   ) {
-    super(args, maxDepth, trackIds);
+    super(trace, uri, maxDepth, trackIds);
   }
 
   onSliceClick(args: OnSliceClickArgs<Slice>) {
@@ -96,12 +96,7 @@
         trackIds,
         kind: SLICE_TRACK_KIND,
       },
-      track: new SuspendResumeSliceTrack(
-        {uri, trace: ctx},
-        maxDepth,
-        trackIds,
-        threads,
-      ),
+      track: new SuspendResumeSliceTrack(ctx, uri, maxDepth, trackIds, threads),
     });
 
     // Display the track in the UI.
diff --git a/ui/src/plugins/org.kernel.Wattson/index.ts b/ui/src/plugins/org.kernel.Wattson/index.ts
index 539366d..ebbe946 100644
--- a/ui/src/plugins/org.kernel.Wattson/index.ts
+++ b/ui/src/plugins/org.kernel.Wattson/index.ts
@@ -94,10 +94,7 @@
   readonly queryKey: string;
 
   constructor(trace: Trace, uri: string, queryKey: string) {
-    super({
-      trace,
-      uri,
-    });
+    super(trace, uri);
     this.queryKey = queryKey;
   }
 
diff --git a/ui/src/public/lib/stdlib_docs.ts b/ui/src/public/lib/stdlib_docs.ts
deleted file mode 100644
index fcdcf18..0000000
--- a/ui/src/public/lib/stdlib_docs.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {assetSrc} from '../../base/assets';
-
-// Fetch the stdlib docs
-export async function getStdlibDocs(): Promise<string> {
-  const resp = await fetch(assetSrc('stdlib_docs.json'));
-  const json = await resp.json();
-  return JSON.parse(json);
-}
diff --git a/ui/src/public/lib/tracks/query_counter_track.ts b/ui/src/public/lib/tracks/query_counter_track.ts
index dc25d69..e412f7c 100644
--- a/ui/src/public/lib/tracks/query_counter_track.ts
+++ b/ui/src/public/lib/tracks/query_counter_track.ts
@@ -109,11 +109,7 @@
     private readonly sqlTableName: string,
     options?: Partial<CounterOptions>,
   ) {
-    super({
-      trace,
-      uri,
-      options,
-    });
+    super(trace, uri, options);
   }
 
   getSqlSource(): string {
diff --git a/ui/src/public/lib/tracks/query_slice_track.ts b/ui/src/public/lib/tracks/query_slice_track.ts
index f7455a9..c8065fb 100644
--- a/ui/src/public/lib/tracks/query_slice_track.ts
+++ b/ui/src/public/lib/tracks/query_slice_track.ts
@@ -137,10 +137,7 @@
     uri: string,
     private readonly sqlTableName: string,
   ) {
-    super({
-      trace,
-      uri,
-    });
+    super(trace, uri);
   }
 
   override async getSqlDataSource(): Promise<CustomSqlTableDefConfig> {
diff --git a/ui/src/public/lib/tracks/slice_layout.ts b/ui/src/public/lib/tracks/slice_layout.ts
new file mode 100644
index 0000000..d328d1c
--- /dev/null
+++ b/ui/src/public/lib/tracks/slice_layout.ts
@@ -0,0 +1,81 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+export interface SliceLayoutBase {
+  readonly padding: number; // vertical pixel padding between slices and track.
+  readonly rowSpacing: number; // Spacing between rows.
+
+  // A *guess* at the depth
+  readonly depthGuess?: number;
+
+  // True iff the track is flat (all slices have the same depth
+  // we have an optimisation for this).
+  readonly isFlat?: boolean;
+
+  readonly titleSizePx?: number;
+  readonly subtitleSizePx?: number;
+}
+
+export const SLICE_LAYOUT_BASE_DEFAULTS: SliceLayoutBase = Object.freeze({
+  padding: 3,
+  rowSpacing: 0,
+});
+
+export interface SliceLayoutFixed extends SliceLayoutBase {
+  readonly heightMode: 'FIXED';
+  readonly fixedHeight: number; // Outer height of the track.
+}
+
+export const SLICE_LAYOUT_FIXED_DEFAULTS: SliceLayoutFixed = Object.freeze({
+  ...SLICE_LAYOUT_BASE_DEFAULTS,
+  heightMode: 'FIXED',
+  fixedHeight: 30,
+});
+
+export interface SliceLayoutFitContent extends SliceLayoutBase {
+  readonly heightMode: 'FIT_CONTENT';
+  readonly sliceHeight: number; // Only when heightMode = 'FIT_CONTENT'.
+}
+
+export const SLICE_LAYOUT_FIT_CONTENT_DEFAULTS: SliceLayoutFitContent =
+  Object.freeze({
+    ...SLICE_LAYOUT_BASE_DEFAULTS,
+    heightMode: 'FIT_CONTENT',
+    sliceHeight: 18,
+  });
+
+export interface SliceLayoutFlat extends SliceLayoutBase {
+  readonly heightMode: 'FIXED';
+  readonly fixedHeight: number; // Outer height of the track.
+  readonly depthGuess: 0;
+  readonly isFlat: true;
+}
+
+export const SLICE_LAYOUT_FLAT_DEFAULTS: SliceLayoutFlat = Object.freeze({
+  ...SLICE_LAYOUT_BASE_DEFAULTS,
+  depthGuess: 0,
+  isFlat: true,
+  heightMode: 'FIXED',
+  fixedHeight: 18,
+  titleSizePx: 10,
+  padding: 3,
+});
+
+export type SliceLayout =
+  | SliceLayoutFixed
+  | SliceLayoutFitContent
+  | SliceLayoutFlat;
+
+export const DEFAULT_SLICE_LAYOUT: SliceLayout =
+  SLICE_LAYOUT_FIT_CONTENT_DEFAULTS;