Multiplatform ready Tracing Driver

* Support emit perfetto traces on Android and JVM.
* `0` allocations when emitting a trace section.
* Use `mutableTypes` in Wire, and expose platform specific storage sinks.
* Coroutine based context propagation.
* Make it trivial to support other platforms (like Darwin).

    1.5 ns           0 allocs    TracingOverheadBenchmarkTest.reference
  779   ns           0 allocs    TracingOverheadBenchmarkTest.customTracingNoSink
3,523   ns           0 allocs    TracingOverheadBenchmarkTest.platformTracing
   11.0 ns           0 allocs    TracingOverheadBenchmarkTest.customTracingDisabled
5,510   ns           2 allocs    TracingOverheadBenchmarkTest.customTracingInMemorySink

There are five benchmarks.

* `reference` emits no trace sections (even when tracing is turned on.). This is just purely work.
* `platformTracing` = reference + emit trace sections using the `androidx.tracing` API.
* `customTracingNoSink` = reference + emit trace sections.
   The trace packets go through their full lifecycle (including recycling).
   The only thing we don't do is to encode the packets into the proto stream.
* `customTracingDisabled` emits no trace sections given tracing is disabled.
* `customTracingInMemorySink` = reference + actually encodes the packets into an in-memory proto stream.

Other Interesting observations:

* Note: Trace packet serialization costs are not fully accounted for in the `platformTracing` benchmark.
  The actual serialization happens outside the perview of the benchmark.
* `NoOpSink` is unsurprisingly faster than the platform version (that emits trace sections).
* Tweaking the `bufferSize` on the Okio `Sink` does not make any difference to the benchmark.

Test: Added some JVM tests and tracing overhead benchmarks.
Relnote: Initial commit for Tracing Driver (experimental)
Change-Id: Iaeec406315fbc493339f8b2c1aabdd65c3875919
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index f41573f..972785ea 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -330,6 +330,7 @@
     docs(project(":test:uiautomator:uiautomator"))
     // androidx.textclassifier is not hosted in androidx
     kmpDocs(project(":tracing:tracing"))
+    kmpDocs(project(":tracing:tracing-driver"))
     docs(project(":tracing:tracing-ktx"))
     docs(project(":tracing:tracing-perfetto"))
     docs(project(":tracing:tracing-perfetto-handshake"))
diff --git a/settings.gradle b/settings.gradle
index 3ea7e4a..0145067 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1025,6 +1025,7 @@
 includeProject(":test:uiautomator:integration-tests:testapp", [BuildType.MAIN])
 includeProject(":tracing:tracing")
 includeProject(":tracing:tracing-ktx")
+includeProject(":tracing:tracing-driver")
 includeProject(":tracing:tracing-perfetto")
 includeProject(":tracing:tracing-perfetto-binary")
 includeProject(":tracing:tracing-perfetto-handshake")
diff --git a/tracing/tracing-driver/api/1.3.0-beta01.txt b/tracing/tracing-driver/api/1.3.0-beta01.txt
new file mode 100644
index 0000000..96b7959
--- /dev/null
+++ b/tracing/tracing-driver/api/1.3.0-beta01.txt
@@ -0,0 +1,97 @@
+// Signature format: 4.0
+package androidx.tracing.driver {
+
+  public final class AndroidTraceSink extends androidx.tracing.driver.TraceSink {
+    ctor public AndroidTraceSink(android.content.Context context);
+    ctor public AndroidTraceSink(android.content.Context context, java.io.File traceFile);
+    ctor public AndroidTraceSink(android.content.Context context, okio.BufferedSink bufferedSink, optional kotlin.coroutines.CoroutineContext coroutineContext);
+    method public void close();
+    method public void emit(androidx.tracing.driver.PooledTracePacketArray packetArray);
+    method public void flush();
+  }
+
+  public final class ClockSource_androidKt {
+    method public static inline long nanoTime();
+  }
+
+  public class CounterTrack extends androidx.tracing.driver.Track {
+    ctor public CounterTrack(String name, androidx.tracing.driver.Track parent, optional boolean hasPreamble);
+    method public final void emitDoubleCounterPacket(double value);
+    method public final void emitLongCounterPacket(long value);
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+  public abstract class EventTrack extends androidx.tracing.driver.Track {
+    ctor public EventTrack(androidx.tracing.driver.TraceContext context, boolean hasPreamble, long uuid, androidx.tracing.driver.Track? parent);
+    method public final androidx.tracing.driver.PooledTracePacket? beginPacket(String name, optional java.util.List<java.lang.Long> flowIds);
+    method public final void emitInstantPacket();
+    method public final androidx.tracing.driver.PooledTracePacket? endPacket(String name);
+    method public final inline <T> T trace(String name, kotlin.jvm.functions.Function0<? extends T> block);
+    method public final suspend <T> Object? traceFlow(String name, optional long flowId, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super T>);
+  }
+
+  public abstract class Poolable<T extends androidx.tracing.driver.Poolable<T>> {
+    method public abstract void recycle();
+  }
+
+  public final class PooledTracePacket extends androidx.tracing.driver.Poolable<androidx.tracing.driver.PooledTracePacket> {
+    method public void encodeTracePacket(com.squareup.wire.ProtoWriter writer);
+    method public void recycle();
+  }
+
+  public final class PooledTracePacketArray extends androidx.tracing.driver.Poolable<androidx.tracing.driver.PooledTracePacketArray> {
+    method public androidx.tracing.driver.PooledTracePacket?[] getPooledTracePacketArray();
+    method public void recycle();
+    property public final androidx.tracing.driver.PooledTracePacket?[] pooledTracePacketArray;
+  }
+
+  public class ProcessTrack extends androidx.tracing.driver.EventTrack {
+    ctor public ProcessTrack(androidx.tracing.driver.TraceContext context, int id, String name, optional boolean hasPreamble);
+    method public androidx.tracing.driver.CounterTrack CounterTrack(String name);
+    method public androidx.tracing.driver.ThreadTrack ThreadTrack(int id, String name);
+    method public androidx.tracing.driver.TraceContext getContext();
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+    property public androidx.tracing.driver.TraceContext context;
+  }
+
+  public class ThreadTrack extends androidx.tracing.driver.EventTrack {
+    ctor public ThreadTrack(int id, String name, androidx.tracing.driver.ProcessTrack process, optional boolean hasPreamble);
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+  public class TraceContext implements java.io.Closeable {
+    ctor public TraceContext(int sequenceId, androidx.tracing.driver.TraceSink sink, boolean isEnabled);
+    method public void close();
+    method public final void flush();
+    method public final int getSequenceId();
+    method public final androidx.tracing.driver.TraceSink getSink();
+    method public final boolean isEnabled();
+    property public final boolean isEnabled;
+    property public final int sequenceId;
+    property public final androidx.tracing.driver.TraceSink sink;
+  }
+
+  public final class TraceDriver {
+    ctor public TraceDriver(int sequenceId, androidx.tracing.driver.TraceSink sink, optional boolean isEnabled);
+    method public androidx.tracing.driver.ProcessTrack ProcessTrack(int id, String name);
+    method public androidx.tracing.driver.TraceContext getContext();
+    property public final androidx.tracing.driver.TraceContext context;
+  }
+
+  public abstract class TraceSink implements java.io.Closeable {
+    ctor public TraceSink();
+    method public abstract void emit(androidx.tracing.driver.PooledTracePacketArray pooledPacketArray);
+    method public abstract void flush();
+  }
+
+  public final class Tracing_androidKt {
+    method public static androidx.tracing.driver.ProcessTrack currentProcessTrack(android.content.Context context, androidx.tracing.driver.TraceDriver traceDriver);
+  }
+
+  public abstract class Track {
+    ctor public Track(androidx.tracing.driver.TraceContext context, boolean hasPreamble, long uuid, androidx.tracing.driver.Track? parent);
+    method public abstract androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+}
+
diff --git a/tracing/tracing-driver/api/current.txt b/tracing/tracing-driver/api/current.txt
new file mode 100644
index 0000000..96b7959
--- /dev/null
+++ b/tracing/tracing-driver/api/current.txt
@@ -0,0 +1,97 @@
+// Signature format: 4.0
+package androidx.tracing.driver {
+
+  public final class AndroidTraceSink extends androidx.tracing.driver.TraceSink {
+    ctor public AndroidTraceSink(android.content.Context context);
+    ctor public AndroidTraceSink(android.content.Context context, java.io.File traceFile);
+    ctor public AndroidTraceSink(android.content.Context context, okio.BufferedSink bufferedSink, optional kotlin.coroutines.CoroutineContext coroutineContext);
+    method public void close();
+    method public void emit(androidx.tracing.driver.PooledTracePacketArray packetArray);
+    method public void flush();
+  }
+
+  public final class ClockSource_androidKt {
+    method public static inline long nanoTime();
+  }
+
+  public class CounterTrack extends androidx.tracing.driver.Track {
+    ctor public CounterTrack(String name, androidx.tracing.driver.Track parent, optional boolean hasPreamble);
+    method public final void emitDoubleCounterPacket(double value);
+    method public final void emitLongCounterPacket(long value);
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+  public abstract class EventTrack extends androidx.tracing.driver.Track {
+    ctor public EventTrack(androidx.tracing.driver.TraceContext context, boolean hasPreamble, long uuid, androidx.tracing.driver.Track? parent);
+    method public final androidx.tracing.driver.PooledTracePacket? beginPacket(String name, optional java.util.List<java.lang.Long> flowIds);
+    method public final void emitInstantPacket();
+    method public final androidx.tracing.driver.PooledTracePacket? endPacket(String name);
+    method public final inline <T> T trace(String name, kotlin.jvm.functions.Function0<? extends T> block);
+    method public final suspend <T> Object? traceFlow(String name, optional long flowId, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super T>);
+  }
+
+  public abstract class Poolable<T extends androidx.tracing.driver.Poolable<T>> {
+    method public abstract void recycle();
+  }
+
+  public final class PooledTracePacket extends androidx.tracing.driver.Poolable<androidx.tracing.driver.PooledTracePacket> {
+    method public void encodeTracePacket(com.squareup.wire.ProtoWriter writer);
+    method public void recycle();
+  }
+
+  public final class PooledTracePacketArray extends androidx.tracing.driver.Poolable<androidx.tracing.driver.PooledTracePacketArray> {
+    method public androidx.tracing.driver.PooledTracePacket?[] getPooledTracePacketArray();
+    method public void recycle();
+    property public final androidx.tracing.driver.PooledTracePacket?[] pooledTracePacketArray;
+  }
+
+  public class ProcessTrack extends androidx.tracing.driver.EventTrack {
+    ctor public ProcessTrack(androidx.tracing.driver.TraceContext context, int id, String name, optional boolean hasPreamble);
+    method public androidx.tracing.driver.CounterTrack CounterTrack(String name);
+    method public androidx.tracing.driver.ThreadTrack ThreadTrack(int id, String name);
+    method public androidx.tracing.driver.TraceContext getContext();
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+    property public androidx.tracing.driver.TraceContext context;
+  }
+
+  public class ThreadTrack extends androidx.tracing.driver.EventTrack {
+    ctor public ThreadTrack(int id, String name, androidx.tracing.driver.ProcessTrack process, optional boolean hasPreamble);
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+  public class TraceContext implements java.io.Closeable {
+    ctor public TraceContext(int sequenceId, androidx.tracing.driver.TraceSink sink, boolean isEnabled);
+    method public void close();
+    method public final void flush();
+    method public final int getSequenceId();
+    method public final androidx.tracing.driver.TraceSink getSink();
+    method public final boolean isEnabled();
+    property public final boolean isEnabled;
+    property public final int sequenceId;
+    property public final androidx.tracing.driver.TraceSink sink;
+  }
+
+  public final class TraceDriver {
+    ctor public TraceDriver(int sequenceId, androidx.tracing.driver.TraceSink sink, optional boolean isEnabled);
+    method public androidx.tracing.driver.ProcessTrack ProcessTrack(int id, String name);
+    method public androidx.tracing.driver.TraceContext getContext();
+    property public final androidx.tracing.driver.TraceContext context;
+  }
+
+  public abstract class TraceSink implements java.io.Closeable {
+    ctor public TraceSink();
+    method public abstract void emit(androidx.tracing.driver.PooledTracePacketArray pooledPacketArray);
+    method public abstract void flush();
+  }
+
+  public final class Tracing_androidKt {
+    method public static androidx.tracing.driver.ProcessTrack currentProcessTrack(android.content.Context context, androidx.tracing.driver.TraceDriver traceDriver);
+  }
+
+  public abstract class Track {
+    ctor public Track(androidx.tracing.driver.TraceContext context, boolean hasPreamble, long uuid, androidx.tracing.driver.Track? parent);
+    method public abstract androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+}
+
diff --git a/tracing/tracing-driver/api/res-1.3.0-beta01.txt b/tracing/tracing-driver/api/res-1.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tracing/tracing-driver/api/res-1.3.0-beta01.txt
diff --git a/tracing/tracing-driver/api/res-current.txt b/tracing/tracing-driver/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tracing/tracing-driver/api/res-current.txt
diff --git a/tracing/tracing-driver/api/restricted_1.3.0-beta01.txt b/tracing/tracing-driver/api/restricted_1.3.0-beta01.txt
new file mode 100644
index 0000000..96b7959
--- /dev/null
+++ b/tracing/tracing-driver/api/restricted_1.3.0-beta01.txt
@@ -0,0 +1,97 @@
+// Signature format: 4.0
+package androidx.tracing.driver {
+
+  public final class AndroidTraceSink extends androidx.tracing.driver.TraceSink {
+    ctor public AndroidTraceSink(android.content.Context context);
+    ctor public AndroidTraceSink(android.content.Context context, java.io.File traceFile);
+    ctor public AndroidTraceSink(android.content.Context context, okio.BufferedSink bufferedSink, optional kotlin.coroutines.CoroutineContext coroutineContext);
+    method public void close();
+    method public void emit(androidx.tracing.driver.PooledTracePacketArray packetArray);
+    method public void flush();
+  }
+
+  public final class ClockSource_androidKt {
+    method public static inline long nanoTime();
+  }
+
+  public class CounterTrack extends androidx.tracing.driver.Track {
+    ctor public CounterTrack(String name, androidx.tracing.driver.Track parent, optional boolean hasPreamble);
+    method public final void emitDoubleCounterPacket(double value);
+    method public final void emitLongCounterPacket(long value);
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+  public abstract class EventTrack extends androidx.tracing.driver.Track {
+    ctor public EventTrack(androidx.tracing.driver.TraceContext context, boolean hasPreamble, long uuid, androidx.tracing.driver.Track? parent);
+    method public final androidx.tracing.driver.PooledTracePacket? beginPacket(String name, optional java.util.List<java.lang.Long> flowIds);
+    method public final void emitInstantPacket();
+    method public final androidx.tracing.driver.PooledTracePacket? endPacket(String name);
+    method public final inline <T> T trace(String name, kotlin.jvm.functions.Function0<? extends T> block);
+    method public final suspend <T> Object? traceFlow(String name, optional long flowId, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super T>);
+  }
+
+  public abstract class Poolable<T extends androidx.tracing.driver.Poolable<T>> {
+    method public abstract void recycle();
+  }
+
+  public final class PooledTracePacket extends androidx.tracing.driver.Poolable<androidx.tracing.driver.PooledTracePacket> {
+    method public void encodeTracePacket(com.squareup.wire.ProtoWriter writer);
+    method public void recycle();
+  }
+
+  public final class PooledTracePacketArray extends androidx.tracing.driver.Poolable<androidx.tracing.driver.PooledTracePacketArray> {
+    method public androidx.tracing.driver.PooledTracePacket?[] getPooledTracePacketArray();
+    method public void recycle();
+    property public final androidx.tracing.driver.PooledTracePacket?[] pooledTracePacketArray;
+  }
+
+  public class ProcessTrack extends androidx.tracing.driver.EventTrack {
+    ctor public ProcessTrack(androidx.tracing.driver.TraceContext context, int id, String name, optional boolean hasPreamble);
+    method public androidx.tracing.driver.CounterTrack CounterTrack(String name);
+    method public androidx.tracing.driver.ThreadTrack ThreadTrack(int id, String name);
+    method public androidx.tracing.driver.TraceContext getContext();
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+    property public androidx.tracing.driver.TraceContext context;
+  }
+
+  public class ThreadTrack extends androidx.tracing.driver.EventTrack {
+    ctor public ThreadTrack(int id, String name, androidx.tracing.driver.ProcessTrack process, optional boolean hasPreamble);
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+  public class TraceContext implements java.io.Closeable {
+    ctor public TraceContext(int sequenceId, androidx.tracing.driver.TraceSink sink, boolean isEnabled);
+    method public void close();
+    method public final void flush();
+    method public final int getSequenceId();
+    method public final androidx.tracing.driver.TraceSink getSink();
+    method public final boolean isEnabled();
+    property public final boolean isEnabled;
+    property public final int sequenceId;
+    property public final androidx.tracing.driver.TraceSink sink;
+  }
+
+  public final class TraceDriver {
+    ctor public TraceDriver(int sequenceId, androidx.tracing.driver.TraceSink sink, optional boolean isEnabled);
+    method public androidx.tracing.driver.ProcessTrack ProcessTrack(int id, String name);
+    method public androidx.tracing.driver.TraceContext getContext();
+    property public final androidx.tracing.driver.TraceContext context;
+  }
+
+  public abstract class TraceSink implements java.io.Closeable {
+    ctor public TraceSink();
+    method public abstract void emit(androidx.tracing.driver.PooledTracePacketArray pooledPacketArray);
+    method public abstract void flush();
+  }
+
+  public final class Tracing_androidKt {
+    method public static androidx.tracing.driver.ProcessTrack currentProcessTrack(android.content.Context context, androidx.tracing.driver.TraceDriver traceDriver);
+  }
+
+  public abstract class Track {
+    ctor public Track(androidx.tracing.driver.TraceContext context, boolean hasPreamble, long uuid, androidx.tracing.driver.Track? parent);
+    method public abstract androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+}
+
diff --git a/tracing/tracing-driver/api/restricted_current.txt b/tracing/tracing-driver/api/restricted_current.txt
new file mode 100644
index 0000000..96b7959
--- /dev/null
+++ b/tracing/tracing-driver/api/restricted_current.txt
@@ -0,0 +1,97 @@
+// Signature format: 4.0
+package androidx.tracing.driver {
+
+  public final class AndroidTraceSink extends androidx.tracing.driver.TraceSink {
+    ctor public AndroidTraceSink(android.content.Context context);
+    ctor public AndroidTraceSink(android.content.Context context, java.io.File traceFile);
+    ctor public AndroidTraceSink(android.content.Context context, okio.BufferedSink bufferedSink, optional kotlin.coroutines.CoroutineContext coroutineContext);
+    method public void close();
+    method public void emit(androidx.tracing.driver.PooledTracePacketArray packetArray);
+    method public void flush();
+  }
+
+  public final class ClockSource_androidKt {
+    method public static inline long nanoTime();
+  }
+
+  public class CounterTrack extends androidx.tracing.driver.Track {
+    ctor public CounterTrack(String name, androidx.tracing.driver.Track parent, optional boolean hasPreamble);
+    method public final void emitDoubleCounterPacket(double value);
+    method public final void emitLongCounterPacket(long value);
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+  public abstract class EventTrack extends androidx.tracing.driver.Track {
+    ctor public EventTrack(androidx.tracing.driver.TraceContext context, boolean hasPreamble, long uuid, androidx.tracing.driver.Track? parent);
+    method public final androidx.tracing.driver.PooledTracePacket? beginPacket(String name, optional java.util.List<java.lang.Long> flowIds);
+    method public final void emitInstantPacket();
+    method public final androidx.tracing.driver.PooledTracePacket? endPacket(String name);
+    method public final inline <T> T trace(String name, kotlin.jvm.functions.Function0<? extends T> block);
+    method public final suspend <T> Object? traceFlow(String name, optional long flowId, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super T>);
+  }
+
+  public abstract class Poolable<T extends androidx.tracing.driver.Poolable<T>> {
+    method public abstract void recycle();
+  }
+
+  public final class PooledTracePacket extends androidx.tracing.driver.Poolable<androidx.tracing.driver.PooledTracePacket> {
+    method public void encodeTracePacket(com.squareup.wire.ProtoWriter writer);
+    method public void recycle();
+  }
+
+  public final class PooledTracePacketArray extends androidx.tracing.driver.Poolable<androidx.tracing.driver.PooledTracePacketArray> {
+    method public androidx.tracing.driver.PooledTracePacket?[] getPooledTracePacketArray();
+    method public void recycle();
+    property public final androidx.tracing.driver.PooledTracePacket?[] pooledTracePacketArray;
+  }
+
+  public class ProcessTrack extends androidx.tracing.driver.EventTrack {
+    ctor public ProcessTrack(androidx.tracing.driver.TraceContext context, int id, String name, optional boolean hasPreamble);
+    method public androidx.tracing.driver.CounterTrack CounterTrack(String name);
+    method public androidx.tracing.driver.ThreadTrack ThreadTrack(int id, String name);
+    method public androidx.tracing.driver.TraceContext getContext();
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+    property public androidx.tracing.driver.TraceContext context;
+  }
+
+  public class ThreadTrack extends androidx.tracing.driver.EventTrack {
+    ctor public ThreadTrack(int id, String name, androidx.tracing.driver.ProcessTrack process, optional boolean hasPreamble);
+    method public androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+  public class TraceContext implements java.io.Closeable {
+    ctor public TraceContext(int sequenceId, androidx.tracing.driver.TraceSink sink, boolean isEnabled);
+    method public void close();
+    method public final void flush();
+    method public final int getSequenceId();
+    method public final androidx.tracing.driver.TraceSink getSink();
+    method public final boolean isEnabled();
+    property public final boolean isEnabled;
+    property public final int sequenceId;
+    property public final androidx.tracing.driver.TraceSink sink;
+  }
+
+  public final class TraceDriver {
+    ctor public TraceDriver(int sequenceId, androidx.tracing.driver.TraceSink sink, optional boolean isEnabled);
+    method public androidx.tracing.driver.ProcessTrack ProcessTrack(int id, String name);
+    method public androidx.tracing.driver.TraceContext getContext();
+    property public final androidx.tracing.driver.TraceContext context;
+  }
+
+  public abstract class TraceSink implements java.io.Closeable {
+    ctor public TraceSink();
+    method public abstract void emit(androidx.tracing.driver.PooledTracePacketArray pooledPacketArray);
+    method public abstract void flush();
+  }
+
+  public final class Tracing_androidKt {
+    method public static androidx.tracing.driver.ProcessTrack currentProcessTrack(android.content.Context context, androidx.tracing.driver.TraceDriver traceDriver);
+  }
+
+  public abstract class Track {
+    ctor public Track(androidx.tracing.driver.TraceContext context, boolean hasPreamble, long uuid, androidx.tracing.driver.Track? parent);
+    method public abstract androidx.tracing.driver.PooledTracePacket? preamblePacket();
+  }
+
+}
+
diff --git a/tracing/tracing-driver/build.gradle b/tracing/tracing-driver/build.gradle
new file mode 100644
index 0000000..e48879c
--- /dev/null
+++ b/tracing/tracing-driver/build.gradle
@@ -0,0 +1,121 @@
+/*
+ * Copyright 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 androidx.build.KotlinTarget
+import androidx.build.PlatformIdentifier
+import androidx.build.SoftwareType
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("androidx.benchmark")
+    id("com.squareup.wire")
+}
+
+androidXMultiplatform {
+    jvm()
+    android()
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                api(libs.kotlinStdlib)
+                api(libs.kotlinCoroutinesCore)
+                api(libs.androidx.annotation)
+
+                implementation("androidx.collection:collection:1.4.5")
+                implementation(libs.wireRuntime)
+                implementation(libs.okio)
+            }
+        }
+        commonTest {
+            dependencies {
+                implementation(libs.kotlinTestAnnotationsCommon)
+                implementation(libs.kotlinTest)
+                implementation(libs.kotlinCoroutinesCore)
+                implementation(libs.kotlinCoroutinesTest)
+            }
+        }
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+                implementation(libs.kotlinTestJunit)
+                implementation(libs.truth)
+            }
+        }
+        jvmMain {
+            dependsOn(commonMain)
+        }
+        androidMain {
+            dependsOn(commonMain)
+        }
+        androidInstrumentedTest {
+            dependsOn(commonTest)
+            dependencies {
+                implementation(projectOrArtifact(":benchmark:benchmark-junit4"))
+                implementation(libs.junit)
+                implementation(libs.testExtJunit)
+                implementation(libs.testCore)
+                implementation(libs.testRunner)
+                implementation(libs.testRules)
+            }
+        }
+    }
+}
+
+// Workarounds for Wire's plugin not setting code generation directory as task output correctly
+// See https://github.com/square/wire/issues/3199
+tasks.named("multiplatformSourceJar").configure {
+    dependsOn(tasks.named("generateCommonMainProtos"))
+}
+
+wire {
+    kotlin {
+        mutableTypes = true
+    }
+    sourcePath {
+        srcDir("src/commonMain/proto")
+    }
+}
+
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions {
+        // Make sure to opt-in to expect-actual classes.
+        // https://youtrack.jetbrains.com/issue/KT-61573
+        freeCompilerArgs += [
+                "-Xexpect-actual-classes",
+        ]
+    }
+}
+
+androidx {
+    name = "Tracing Driver"
+    type = SoftwareType.SNAPSHOT_ONLY_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
+    inceptionYear = "2024"
+    description = "AndroidX Tracing Driver"
+    metalavaK2UastEnabled = false
+    kotlinTarget = KotlinTarget.KOTLIN_2_0
+}
+
+android {
+    namespace = "androidx.tracing.driver"
+    defaultConfig {
+        minSdk = 23
+    }
+}
diff --git a/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/AndroidManifest.xml b/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/AndroidManifest.xml
new file mode 100644
index 0000000..c5797ba
--- /dev/null
+++ b/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2025 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <application>
+        <!-- enable profiling by shell for non-intrusive profiling tools -->
+        <profileable android:shell="true" />
+    </application>
+</manifest>
diff --git a/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/androidx/tracing/driver/Sinks.android.kt b/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/androidx/tracing/driver/Sinks.android.kt
new file mode 100644
index 0000000..f85ba26d
--- /dev/null
+++ b/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/androidx/tracing/driver/Sinks.android.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import android.content.Context
+import okio.blackholeSink
+import okio.buffer
+
+internal fun buildInMemorySink(context: Context): AndroidTraceSink {
+    val buffer = blackholeSink().buffer()
+    return AndroidTraceSink(context, buffer)
+}
diff --git a/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/androidx/tracing/driver/TracingOverheadBenchmarkTest.kt b/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/androidx/tracing/driver/TracingOverheadBenchmarkTest.kt
new file mode 100644
index 0000000..3239a1b
--- /dev/null
+++ b/tracing/tracing-driver/src/androidInstrumentedTest/kotlin/androidx/tracing/driver/TracingOverheadBenchmarkTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import android.content.Context
+import androidx.benchmark.ExperimentalBenchmarkConfigApi
+import androidx.benchmark.MicrobenchmarkConfig
+import androidx.benchmark.TimeCapture
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.tracing.Trace
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+internal const val PROCESS_NAME = "process"
+internal const val TRACE_TAG = "work"
+
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalBenchmarkConfigApi::class)
+@LargeTest
+class TracingOverheadBenchmarkTest {
+
+    val config =
+        MicrobenchmarkConfig(
+            traceAppTagEnabled = true,
+            metrics = listOf(TimeCapture()),
+        )
+
+    @get:Rule val benchmarkRule = BenchmarkRule(config)
+
+    private fun buildTraceContext(sink: TraceSink): TraceContext {
+        return TraceContext(sequenceId = 1, sink = sink, isEnabled = true)
+    }
+
+    @Test
+    fun reference() {
+        benchmarkRule.measureRepeated {}
+    }
+
+    @Test
+    fun platformTracing() {
+        benchmarkRule.measureRepeated {
+            Trace.beginSection(TRACE_TAG)
+            Trace.endSection()
+        }
+    }
+
+    @Test
+    fun customTracingNoSink() {
+        val traceContext = buildTraceContext(NoOpSink())
+        val process = traceContext.ProcessTrack(id = 10, name = PROCESS_NAME)
+        traceContext.use { benchmarkRule.measureRepeated { process.trace(TRACE_TAG) {} } }
+    }
+
+    @Test
+    fun customTracingDisabled() {
+        val context = EmptyTraceContext
+        val process = context.ProcessTrack(id = 10, name = PROCESS_NAME)
+        context.use { benchmarkRule.measureRepeated { process.trace(TRACE_TAG) {} } }
+    }
+
+    @Test
+    fun customTracingInMemorySink() {
+        val context = ApplicationProvider.getApplicationContext<Context>()
+        val sink = buildInMemorySink(context)
+        val traceContext = buildTraceContext(sink)
+        traceContext.use {
+            val process = traceContext.ProcessTrack(id = 10, name = PROCESS_NAME)
+            var count = 0
+            benchmarkRule.measureRepeated {
+                process.trace(TRACE_TAG) {}
+                // The Benchmark measurement loop creates ~250k packets when measuring the cost
+                // of this loop. So we essentially give our sink a chance to catch up every 1000
+                // packets.
+                runWithMeasurementDisabled {
+                    count += 1
+                    if (count >= 1000) {
+                        @SuppressWarnings("BanThreadSleep")
+                        Thread.sleep(500L) // Just slow down the producer a bit
+                        count = 0
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/AtomicBoolean.android.kt b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/AtomicBoolean.android.kt
new file mode 100644
index 0000000..9446267
--- /dev/null
+++ b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/AtomicBoolean.android.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+public actual typealias AtomicBoolean = AtomicBoolean
diff --git a/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/AtomicLong.android.kt b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/AtomicLong.android.kt
new file mode 100644
index 0000000..60db600
--- /dev/null
+++ b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/AtomicLong.android.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import java.util.concurrent.atomic.AtomicLong
+
+public actual typealias AtomicLong = AtomicLong
diff --git a/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/ClockSource.android.kt b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/ClockSource.android.kt
new file mode 100644
index 0000000..f6763fb
--- /dev/null
+++ b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/ClockSource.android.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+@Suppress("NOTHING_TO_INLINE") public actual inline fun nanoTime(): Long = System.nanoTime()
diff --git a/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/Lock.android.kt b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/Lock.android.kt
new file mode 100644
index 0000000..93ccff1
--- /dev/null
+++ b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/Lock.android.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import java.util.concurrent.locks.ReentrantLock
+
+public actual typealias Lock = ReentrantLock
diff --git a/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/TraceSink.android.kt b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/TraceSink.android.kt
new file mode 100644
index 0000000..7fc7960
--- /dev/null
+++ b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/TraceSink.android.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import android.content.Context
+import com.squareup.wire.ProtoWriter
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import okio.BufferedSink
+import okio.appendingSink
+import okio.buffer
+
+/** The trace sink that writes to a new file per trace session. */
+@SuppressWarnings("StreamFiles") // Accepts a BufferedSink instead.
+public class AndroidTraceSink(
+    context: Context,
+    private val bufferedSink: BufferedSink,
+    private val coroutineContext: CoroutineContext = Dispatchers.IO
+) : TraceSink() {
+
+    public constructor(
+        context: Context
+    ) : this(context = context, traceFile = context.noBackupFilesDir.perfettoTraceFile())
+
+    @SuppressWarnings("StreamFiles") // Accepting a BufferedSink instead.
+    public constructor(
+        context: Context,
+        traceFile: File,
+    ) : this(context = context, bufferedSink = traceFile.appendingSink().buffer())
+
+    // Hold on to the application context.
+    @Suppress("UNUSED") private val context = context.applicationContext
+    private val protoWriter: ProtoWriter = ProtoWriter(bufferedSink)
+
+    // There are 2 distinct mechanisms for thread safety here, and they are not necessarily in sync.
+    // The Queue by itself is thread-safe, but after we drain the queue we mark drainRequested
+    // to false (not an atomic operation). So a writer can come along and add a pooled array of
+    // trace packets. That is still okay given, those packets will get picked during the next
+    // drain request; or on flush() prior to the close() of the Sink.
+    // No packets are lost or dropped; and therefore we are still okay with this small
+    // compromise with thread safety.
+    private val queue = Queue<PooledTracePacketArray>()
+    private val drainRequested = AtomicBoolean(false)
+
+    @Volatile private var resumeDrain: Continuation<Unit>
+
+    init {
+        resumeDrain =
+            suspend {
+                    coroutineContext[Job]?.invokeOnCompletion { makeDrainRequest() }
+                    while (true) {
+                        drainQueue() // Sets drainRequested to false on completion
+                        suspendCoroutine<Unit> { continuation ->
+                            resumeDrain = continuation
+                            COROUTINE_SUSPENDED // Suspend
+                        }
+                    }
+                }
+                .createCoroutineUnintercepted(Continuation(context = coroutineContext) {})
+
+        // Kick things off and suspend
+        makeDrainRequest()
+    }
+
+    override fun emit(packetArray: PooledTracePacketArray) {
+        queue.addFirst(packetArray)
+        makeDrainRequest()
+    }
+
+    override fun flush() {
+        makeDrainRequest()
+        while (drainRequested.get()) {
+            // Await completion of the drain.
+        }
+        bufferedSink.flush()
+    }
+
+    private fun makeDrainRequest() {
+        // Only make a request if one is not already ongoing
+        if (drainRequested.compareAndSet(false, true)) {
+            resumeDrain.resume(Unit)
+        }
+    }
+
+    private fun drainQueue() {
+        while (queue.isNotEmpty()) {
+            val pooledPacketArray = queue.removeFirstOrNull()
+            if (pooledPacketArray != null) {
+                for (pooledTracePacket in pooledPacketArray.pooledTracePacketArray) {
+                    if (pooledTracePacket != null) {
+                        pooledTracePacket.encodeTracePacket(protoWriter)
+                        pooledTracePacket.recycle()
+                    }
+                }
+                pooledPacketArray.recycle()
+            }
+        }
+        drainRequested.set(false)
+    }
+
+    override fun close() {
+        makeDrainRequest()
+        bufferedSink.close()
+    }
+}
+
+private fun File.perfettoTraceFile(): File {
+    val formatter = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
+    formatter.timeZone = TimeZone.getTimeZone("UTC")
+    val traceFile = File(this, "perfetto-${formatter.format(Date())}.perfetto-trace")
+    return traceFile
+}
diff --git a/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/Tracing.android.kt b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/Tracing.android.kt
new file mode 100644
index 0000000..ed779d6
--- /dev/null
+++ b/tracing/tracing-driver/src/androidMain/kotlin/androidx/tracing/driver/Tracing.android.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import android.content.Context
+import android.os.Build
+import android.os.Process
+
+/** Return the [ProcessTrack] given the [Context] and the [TraceDriver] instance. */
+public fun currentProcessTrack(context: Context, traceDriver: TraceDriver): ProcessTrack {
+    val pid = Process.myPid()
+    val name =
+        if (Build.VERSION.SDK_INT >= 33) {
+            Process.myProcessName()
+        } else {
+            context.packageName
+        }
+
+    return traceDriver.ProcessTrack(id = pid, name = name)
+}
diff --git a/tracing/tracing-driver/src/androidMain/kotlin/perfetto/protos/package-info.java b/tracing/tracing-driver/src/androidMain/kotlin/perfetto/protos/package-info.java
new file mode 100644
index 0000000..69d8393
--- /dev/null
+++ b/tracing/tracing-driver/src/androidMain/kotlin/perfetto/protos/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2025 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.
+ */
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+package perfetto.protos;
+
+import androidx.annotation.RestrictTo;
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/AtomicBoolean.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/AtomicBoolean.kt
new file mode 100644
index 0000000..36816ed
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/AtomicBoolean.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+public expect class AtomicBoolean(initial: Boolean) {
+    public fun get(): Boolean
+
+    public fun set(newValue: Boolean)
+
+    public fun compareAndSet(expected: Boolean, newValue: Boolean): Boolean
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/AtomicLong.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/AtomicLong.kt
new file mode 100644
index 0000000..a7d94c1
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/AtomicLong.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+public expect class AtomicLong(initial: Long) {
+    public fun incrementAndGet(): Long
+
+    public fun compareAndSet(expected: Long, actual: Long): Boolean
+
+    public fun get(): Long
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun AtomicLong.decrement(): Boolean {
+    val current = get()
+    return compareAndSet(current, current - 1)
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ClockSource.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ClockSource.kt
new file mode 100644
index 0000000..129f121
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ClockSource.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+public expect fun nanoTime(): Long
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Common.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Common.kt
new file mode 100644
index 0000000..6b8e4e9
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Common.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+internal val id = AtomicLong(42L)
+
+/** Generates a monotonically increasing [Long] value. */
+internal fun monotonicId(): Long {
+    return id.incrementAndGet()
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ContextElements.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ContextElements.kt
new file mode 100644
index 0000000..e08e91b
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ContextElements.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import kotlin.coroutines.AbstractCoroutineContextElement
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.coroutineContext
+
+/** An [AbstractCoroutineContextElement] that can hold to the [List] of current `flowId`s.. */
+internal class FlowContextElement(internal val flowIds: List<Long>) :
+    AbstractCoroutineContextElement(KEY) {
+
+    internal companion object {
+        internal val KEY: CoroutineContext.Key<FlowContextElement> =
+            object : CoroutineContext.Key<FlowContextElement> {}
+    }
+}
+
+/** Useful in the context of structured concurrency to keep track of flows. */
+internal suspend fun obtainFlowContext(): FlowContextElement? {
+    return coroutineContext[FlowContextElement.KEY]
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/CounterTrack.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/CounterTrack.kt
new file mode 100644
index 0000000..81aeafb
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/CounterTrack.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+/** Represents a Perfetto Counter track. */
+public open class CounterTrack(
+    /** The name of the counter track */
+    private val name: String,
+    /** The parent track the counter belongs to. */
+    private val parent: Track,
+    hasPreamble: Boolean = true,
+) :
+    Track(
+        context = parent.context,
+        hasPreamble = hasPreamble,
+        uuid = monotonicId(),
+        parent = parent
+    ) {
+    override fun preamblePacket(): PooledTracePacket? {
+        val packet = context.pool.obtainTracePacket()
+        val track = context.pool.obtainTrackDescriptor()
+        val counter = context.pool.obtainCounterDescriptor()
+        packet.trackPoolableForOwnership(track)
+        packet.trackPoolableForOwnership(counter)
+        track.trackDescriptor.uuid = uuid
+        track.trackDescriptor.name = name
+        track.trackDescriptor.parent_uuid = parent.uuid
+        track.trackDescriptor.counter = counter.counterDescriptor
+        packet.tracePacket.timestamp = nanoTime()
+        packet.tracePacket.track_descriptor = track.trackDescriptor
+        return packet
+    }
+
+    public fun emitLongCounterPacket(value: Long) {
+        if (context.isEnabled) {
+            emit(longCounterPacket(value))
+        }
+    }
+
+    public fun emitDoubleCounterPacket(value: Double) {
+        if (context.isEnabled) {
+            emit(doubleCounterPacket(value))
+        }
+    }
+}
+
+// An empty counter track when tracing is disabled
+
+private const val EMPTY_COUNTER_NAME = "Empty Counter"
+
+internal class EmptyCounterTrack(process: EmptyProcessTrack) :
+    CounterTrack(name = EMPTY_COUNTER_NAME, parent = process, hasPreamble = false) {
+    override fun preamblePacket(): PooledTracePacket? = null
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/EventTrack.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/EventTrack.kt
new file mode 100644
index 0000000..ab18362
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/EventTrack.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.withContext
+
+/** Entities that we can attach traces to to be visualized on the timeline with slices & flows. */
+public abstract class EventTrack(
+    /** The [TraceContext] instance. */
+    context: TraceContext,
+    /** `true` iff we need to emit some preamble packets. */
+    hasPreamble: Boolean,
+    /** The uuid for the track descriptor. */
+    uuid: Long,
+    /** The parent traceable. */
+    parent: Track?
+) : Track(context = context, hasPreamble = hasPreamble, uuid = uuid, parent = parent) {
+
+    public fun beginPacket(name: String, flowIds: List<Long> = emptyList()): PooledTracePacket? {
+        return if (!context.isEnabled) {
+            null
+        } else {
+            trackBeginPacket(name, flowIds)
+        }
+    }
+
+    public fun endPacket(name: String): PooledTracePacket? {
+        return if (!context.isEnabled) {
+            null
+        } else {
+            trackEndPacket(name)
+        }
+    }
+
+    public fun emitInstantPacket() {
+        if (context.isEnabled) {
+            instantPacket()
+        }
+    }
+
+    public inline fun <T> trace(name: String, crossinline block: () -> T): T {
+        if (context.isEnabled) {
+            val packet = beginPacket(name)
+            if (packet != null) {
+                emit(packet)
+            }
+        }
+        try {
+            return block()
+        } finally {
+            if (context.isEnabled) {
+                val packet = endPacket(name)
+                if (packet != null) {
+                    emit(packet)
+                }
+            }
+        }
+    }
+
+    /** [Track] scoped async trace slices. */
+    public suspend fun <T> traceFlow(
+        name: String,
+        flowId: Long = monotonicId(),
+        block: suspend () -> T
+    ): T {
+        return if (!context.isEnabled) {
+            block()
+        } else {
+            traceFlow(name = name, flowIds = listOf(flowId), block = block)
+        }
+    }
+
+    /** [Track] scoped async trace slices. */
+    private suspend fun <T, R : Track> R.traceFlow(
+        name: String,
+        flowIds: List<Long>,
+        block: suspend () -> T
+    ): T {
+        val element = obtainFlowContext()
+        val newFlowIds =
+            if (element == null) {
+                flowIds
+            } else {
+                element.flowIds + flowIds
+            }
+        val newElement = FlowContextElement(flowIds = newFlowIds)
+        return withContext(coroutineContext + newElement) {
+            val begin = beginPacket(name = name, flowIds = newFlowIds)
+            if (begin != null) {
+                emit(begin)
+            }
+            try {
+                block()
+            } finally {
+                val end = endPacket(name)
+                if (end != null) {
+                    emit(end)
+                }
+            }
+        }
+    }
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Lock.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Lock.kt
new file mode 100644
index 0000000..52d4739
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Lock.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+/** Represents a synchronization primitive that can be used. */
+public expect class Lock() {
+    public fun lock()
+
+    public fun unlock()
+}
+
+internal inline fun <T> Lock.withLock(crossinline block: () -> T): T {
+    try {
+        lock()
+        return block()
+    } finally {
+        unlock()
+    }
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Pool.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Pool.kt
new file mode 100644
index 0000000..108e9d0
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Pool.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+/**
+ * An Object pool that keeps track of scrap objects in a fixed size [ArrayDeque].
+ *
+ * @param size represents the size of the Object pool.
+ * @param factory is a function that can be used to create the instances of the Object for the pool.
+ * @param T represents an object that be re-used.
+ */
+internal class Pool<T>(
+    private val size: Int,
+    private val isDebug: Boolean,
+    private val factory: (owner: Pool<T>) -> T
+) {
+    private var counter: AtomicLong? = null
+
+    init {
+        if (isDebug) {
+            counter = AtomicLong(0L)
+        }
+    }
+
+    // This class is intentionally lock free.
+    // This is because, the only place where we recycle objects in the pool is in the TraceSink
+    // and that effectively behaves as-if it were single threaded.
+    private val scrapPool: ArrayDeque<T> = ArrayDeque(size)
+
+    init {
+        // Eagerly create the objects for the pool
+        for (i in 0 until size) {
+            scrapPool.addFirst(factory(this))
+        }
+    }
+
+    /** Obtain an instance of the object from the pool. */
+    internal fun obtain(): T {
+        if (isDebug) {
+            counter?.incrementAndGet()
+        }
+        return scrapPool.removeFirstOrNull() ?: factory(this)
+    }
+
+    internal fun release(element: T) {
+        if (isDebug) {
+            counter?.decrement()
+        }
+        if (scrapPool.size < size) {
+            scrapPool.addFirst(element)
+        }
+    }
+
+    internal fun count(): Long {
+        return counter?.get() ?: 0L
+    }
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Poolable.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Poolable.kt
new file mode 100644
index 0000000..3a0c1e3
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Poolable.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+/** Represents an object that can be reused using a [Pool]. This avoids GC churn. */
+public abstract class Poolable<T : Poolable<T>>
+internal constructor(internal open val owner: Pool<T>) {
+    /** Recycles the object, and hands it back to the pool. */
+    public abstract fun recycle()
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/PooledProtos.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/PooledProtos.kt
new file mode 100644
index 0000000..1477a12
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/PooledProtos.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import com.squareup.wire.ProtoWriter
+import okio.ByteString
+import perfetto.protos.MutableCounterDescriptor
+import perfetto.protos.MutableProcessDescriptor
+import perfetto.protos.MutableThreadDescriptor
+import perfetto.protos.MutableTracePacket
+import perfetto.protos.MutableTrackDescriptor
+import perfetto.protos.MutableTrackEvent
+
+// Pooled versions of the underlying protos being used.
+
+internal class PooledTrackEvent(owner: Pool<PooledTrackEvent>, val trackEvent: MutableTrackEvent) :
+    Poolable<PooledTrackEvent>(owner) {
+    override fun recycle() {
+        trackEvent.name_iid = null
+        trackEvent.name = null
+        trackEvent.type = null
+        trackEvent.track_uuid = null
+        trackEvent.counter_value = null
+        trackEvent.double_counter_value = null
+        trackEvent.unknownFields = ByteString.EMPTY
+        trackEvent.categories = emptyList()
+        trackEvent.extra_counter_track_uuids = emptyList()
+        trackEvent.extra_counter_values = emptyList()
+        trackEvent.extra_double_counter_track_uuids = emptyList()
+        trackEvent.extra_double_counter_values = emptyList()
+        trackEvent.flow_ids = emptyList()
+        owner.release(this)
+    }
+}
+
+public class PooledTracePacket
+internal constructor(
+    owner: Pool<PooledTracePacket>,
+    // Internal for testing
+    internal val tracePacket: MutableTracePacket,
+    // We are keeping track of some associated Poolables this way, so they can consistently
+    // be recycled correctly. A size of `4` ought to be big enough for our needs.
+    private val nested: Array<Poolable<*>?> = arrayOfNulls(4)
+) : Poolable<PooledTracePacket>(owner) {
+    private var nestedIndex = 0
+
+    override fun recycle() {
+        for (i in 0 until nestedIndex) {
+            val poolable = nested[i]
+            poolable?.recycle()
+            nested[i] = null
+        }
+        nestedIndex = 0
+        tracePacket.timestamp = INVALID_LONG
+        tracePacket.timestamp_clock_id = null
+        tracePacket.track_event = null
+        tracePacket.track_descriptor = null
+        tracePacket.trace_uuid = null
+        tracePacket.compressed_packets = null
+        tracePacket.trusted_packet_sequence_id = null
+        tracePacket.interned_data = null
+        tracePacket.sequence_flags = null
+        tracePacket.incremental_state_cleared = null
+        tracePacket.trace_packet_defaults = null
+        tracePacket.unknownFields = ByteString.EMPTY
+        owner.release(this)
+    }
+
+    internal fun trackPoolableForOwnership(poolable: Poolable<*>) {
+        nested[nestedIndex] = poolable
+        nestedIndex += 1
+    }
+
+    public fun encodeTracePacket(writer: ProtoWriter) {
+        MutableTracePacket.ADAPTER.encodeWithTag(writer, 1, tracePacket)
+    }
+}
+
+internal class PooledTrackDescriptor(
+    owner: Pool<PooledTrackDescriptor>,
+    val trackDescriptor: MutableTrackDescriptor
+) : Poolable<PooledTrackDescriptor>(owner) {
+    override fun recycle() {
+        trackDescriptor.uuid = null
+        trackDescriptor.name = null
+        trackDescriptor.parent_uuid = null
+        trackDescriptor.process = null
+        trackDescriptor.thread = null
+        trackDescriptor.counter = null
+        trackDescriptor.disallow_merging_with_system_tracks = null
+        trackDescriptor.unknownFields = ByteString.EMPTY
+        owner.release(this)
+    }
+}
+
+internal class PooledProcessDescriptor(
+    owner: Pool<PooledProcessDescriptor>,
+    val processDescriptor: MutableProcessDescriptor
+) : Poolable<PooledProcessDescriptor>(owner) {
+    override fun recycle() {
+        processDescriptor.pid = INVALID_INT
+        processDescriptor.cmdline = emptyList()
+        processDescriptor.process_name = null
+        processDescriptor.process_labels = emptyList()
+        processDescriptor.unknownFields = ByteString.EMPTY
+        owner.release(this)
+    }
+}
+
+internal class PooledThreadDescriptor(
+    owner: Pool<PooledThreadDescriptor>,
+    val threadDescriptor: MutableThreadDescriptor
+) : Poolable<PooledThreadDescriptor>(owner) {
+    override fun recycle() {
+        threadDescriptor.pid = INVALID_INT
+        threadDescriptor.tid = INVALID_INT
+        threadDescriptor.thread_name = null
+        threadDescriptor.unknownFields = ByteString.EMPTY
+        owner.release(this)
+    }
+}
+
+internal class PooledCounterDescriptor(
+    owner: Pool<PooledCounterDescriptor>,
+    val counterDescriptor: MutableCounterDescriptor
+) : Poolable<PooledCounterDescriptor>(owner) {
+    override fun recycle() {
+        counterDescriptor.type = null
+        counterDescriptor.categories = emptyList()
+        counterDescriptor.unit = null
+        counterDescriptor.unit_name = null
+        counterDescriptor.unit_multiplier = null
+        counterDescriptor.is_incremental = null
+        counterDescriptor.unknownFields = ByteString.EMPTY
+        owner.release(this)
+    }
+}
+
+public class PooledTracePacketArray
+internal constructor(
+    owner: Pool<PooledTracePacketArray>,
+    @get:SuppressWarnings("NullableCollectionElement") // Object pooling to avoid allocations
+    public val pooledTracePacketArray: Array<PooledTracePacket?>
+) : Poolable<PooledTracePacketArray>(owner) {
+    override fun recycle() {
+        for (i in 0 until pooledTracePacketArray.size) {
+            // Don't recycle the underlying tracePacket because that will happen
+            // once we serialize the packet to the ProtoStream.
+            pooledTracePacketArray[i] = null
+        }
+        owner.release(this)
+    }
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ProcessTrack.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ProcessTrack.kt
new file mode 100644
index 0000000..04d04a7
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ProcessTrack.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import androidx.collection.mutableScatterMapOf
+
+/** Represents a track for a process in a perfetto trace. */
+public open class ProcessTrack(
+    /** The tracing context. */
+    override val context: TraceContext,
+    /** The process id */
+    internal val id: Int,
+    /** The name of the process. */
+    internal val name: String,
+    hasPreamble: Boolean = true,
+) : EventTrack(context = context, hasPreamble = hasPreamble, uuid = monotonicId(), parent = null) {
+    internal val lock = Lock()
+    internal val threads = mutableScatterMapOf<String, ThreadTrack>()
+    internal val counters = mutableScatterMapOf<String, CounterTrack>()
+
+    override fun preamblePacket(): PooledTracePacket? {
+        val packet = context.pool.obtainTracePacket()
+        val track = context.pool.obtainTrackDescriptor()
+        val process = context.pool.obtainProcessDescriptor()
+        packet.trackPoolableForOwnership(track)
+        packet.trackPoolableForOwnership(process)
+        // Populate process details
+        process.processDescriptor.pid = id
+        process.processDescriptor.process_name = name
+        // Link
+        track.trackDescriptor.uuid = uuid
+        track.trackDescriptor.process = process.processDescriptor
+        packet.tracePacket.timestamp = nanoTime()
+        packet.tracePacket.track_descriptor = track.trackDescriptor
+        packet.tracePacket.trusted_packet_sequence_id = context.sequenceId
+        return packet
+    }
+
+    /** @return A [ThreadTrack] for a given [ProcessTrack] using the unique thread [id]. */
+    public open fun ThreadTrack(id: Int, name: String): ThreadTrack {
+        // Thread ids are only unique for lifetime of the thread and can be potentially reused.
+        // Therefore we end up combining the `name` of the thread and its `id` as a key.
+        val key = "$id/$name"
+        return threads[key]
+            ?: lock.withLock {
+                val track =
+                    threads.getOrPut(key) { ThreadTrack(id = id, name = name, process = this) }
+                check(track.name == name)
+                track
+            }
+    }
+
+    /** @return A [CounterTrack] for a given [ProcessTrack] with the provided [name]. */
+    public open fun CounterTrack(name: String): CounterTrack {
+        return counters[name]
+            ?: lock.withLock {
+                counters.getOrPut(name) { CounterTrack(name = name, parent = this) }
+            }
+    }
+}
+
+// An empty process track when tracing is disabled.
+
+private const val EMPTY_PROCESS_ID = -1
+private const val EMPTY_PROCESS_NAME = "Empty Process"
+
+internal class EmptyProcessTrack(override val context: EmptyTraceContext) :
+    ProcessTrack(
+        context = context,
+        id = EMPTY_PROCESS_ID,
+        name = EMPTY_PROCESS_NAME,
+        hasPreamble = false
+    ) {
+    override fun preamblePacket(): PooledTracePacket? = null
+
+    override fun ThreadTrack(id: Int, name: String): ThreadTrack = this.context.thread
+
+    override fun CounterTrack(name: String): CounterTrack = this.context.counter
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ProtoPool.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ProtoPool.kt
new file mode 100644
index 0000000..5bc98a1
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ProtoPool.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import perfetto.protos.MutableCounterDescriptor
+import perfetto.protos.MutableProcessDescriptor
+import perfetto.protos.MutableThreadDescriptor
+import perfetto.protos.MutableTracePacket
+import perfetto.protos.MutableTrackDescriptor
+import perfetto.protos.MutableTrackEvent
+
+// This is 4 * the total number of outstanding requests that can be emitted by a track.
+private const val TRACK_EVENT_POOL_SIZE = 4096
+private const val TRACE_PACKET_POOL_SIZE = 4096
+private const val TRACK_DESCRIPTOR_POOL_SIZE = 256
+private const val PROCESS_DESCRIPTOR_POOL_SIZE = 4
+private const val THREAD_DESCRIPTOR_POOL_SIZE = 8
+private const val COUNTER_DESCRIPTOR_POOL_SIZE = 8
+// The size of the array
+// This would mean that each pool can queue up to 32 * 32 trace packets
+internal const val TRACE_PACKET_BUFFER_SIZE = 32
+// The size of the pool
+private const val TRACE_PACKET_POOL_ARRAY_POOL_SIZE = 32
+
+internal const val INVALID_INT = -1
+internal const val INVALID_LONG = -1L
+
+/** The uber proto pool that knows how to create all the necessary protos. */
+internal class ProtoPool(internal val isDebug: Boolean) {
+
+    internal val tracePacketPool: Pool<PooledTracePacket> =
+        Pool(size = TRACE_PACKET_POOL_SIZE, isDebug = isDebug) { pool ->
+            PooledTracePacket(
+                owner = pool,
+                tracePacket = MutableTracePacket(timestamp = INVALID_LONG),
+            )
+        }
+
+    internal val tracePacketArrayPool: Pool<PooledTracePacketArray> =
+        Pool(size = TRACE_PACKET_POOL_ARRAY_POOL_SIZE, isDebug = isDebug) { pool ->
+            PooledTracePacketArray(
+                owner = pool,
+                pooledTracePacketArray = arrayOfNulls(TRACE_PACKET_BUFFER_SIZE)
+            )
+        }
+
+    internal val trackDescriptorPool =
+        Pool<PooledTrackDescriptor>(size = TRACK_DESCRIPTOR_POOL_SIZE, isDebug = isDebug) { pool ->
+            PooledTrackDescriptor(owner = pool, trackDescriptor = MutableTrackDescriptor())
+        }
+
+    internal val processDescriptorPool =
+        Pool<PooledProcessDescriptor>(size = PROCESS_DESCRIPTOR_POOL_SIZE, isDebug = isDebug) { pool
+            ->
+            PooledProcessDescriptor(
+                owner = pool,
+                processDescriptor = MutableProcessDescriptor(pid = INVALID_INT),
+            )
+        }
+
+    internal val threadDescriptorPool =
+        Pool<PooledThreadDescriptor>(size = THREAD_DESCRIPTOR_POOL_SIZE, isDebug = isDebug) { pool
+            ->
+            PooledThreadDescriptor(
+                owner = pool,
+                threadDescriptor = MutableThreadDescriptor(pid = INVALID_INT, tid = INVALID_INT),
+            )
+        }
+
+    internal val counterDescriptorPool =
+        Pool<PooledCounterDescriptor>(size = COUNTER_DESCRIPTOR_POOL_SIZE, isDebug = isDebug) { pool
+            ->
+            PooledCounterDescriptor(owner = pool, counterDescriptor = MutableCounterDescriptor())
+        }
+
+    internal val trackEventPool =
+        Pool<PooledTrackEvent>(TRACK_EVENT_POOL_SIZE, isDebug = isDebug) { pool ->
+            PooledTrackEvent(owner = pool, trackEvent = MutableTrackEvent())
+        }
+
+    fun obtainTracePacket(): PooledTracePacket {
+        val packet = tracePacketPool.obtain()
+        // Always update time when dealing with recycled packets
+        // This is only being done because `timestamp` is now a required field to avoid boxing.
+        packet.tracePacket.timestamp = nanoTime()
+        return packet
+    }
+
+    fun obtainTracePacketArray(): PooledTracePacketArray {
+        return tracePacketArrayPool.obtain()
+    }
+
+    fun obtainTrackDescriptor(): PooledTrackDescriptor {
+        return trackDescriptorPool.obtain()
+    }
+
+    fun obtainProcessDescriptor(): PooledProcessDescriptor {
+        return processDescriptorPool.obtain()
+    }
+
+    fun obtainThreadDescriptor(): PooledThreadDescriptor {
+        return threadDescriptorPool.obtain()
+    }
+
+    fun obtainCounterDescriptor(): PooledCounterDescriptor {
+        return counterDescriptorPool.obtain()
+    }
+
+    fun obtainTrackEvent(): PooledTrackEvent {
+        return trackEventPool.obtain()
+    }
+
+    // Debug only
+    fun poolableCount(): Long {
+        if (!isDebug) {
+            return 0L
+        }
+
+        var count = 0L
+        count += tracePacketPool.count()
+        count += tracePacketArrayPool.count()
+        count += trackDescriptorPool.count()
+        count += processDescriptorPool.count()
+        count += threadDescriptorPool.count()
+        count += counterDescriptorPool.count()
+        count += trackEventPool.count()
+        return count
+    }
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Queue.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Queue.kt
new file mode 100644
index 0000000..6bc2197
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Queue.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+internal const val QUEUE_CAPACITY = 64
+
+/** An actual thread safe queue implementation. */
+internal class Queue<T>(capacity: Int = QUEUE_CAPACITY) {
+    private val lock = Lock()
+    private val queue: ArrayDeque<T> = ArrayDeque(capacity)
+
+    internal fun isEmpty(): Boolean {
+        return lock.withLock { queue.isEmpty() }
+    }
+
+    internal fun isNotEmpty(): Boolean {
+        return lock.withLock { queue.isNotEmpty() }
+    }
+
+    internal val size
+        get() = { lock.withLock { queue.size } }
+
+    internal fun addFirst(value: T) {
+        lock.withLock { queue.addFirst(value) }
+    }
+
+    internal fun removeFirstOrNull(): T? {
+        return lock.withLock { queue.removeFirstOrNull() }
+    }
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Sink.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Sink.kt
new file mode 100644
index 0000000..bfab3c5
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Sink.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import okio.Closeable
+
+/** A sink that we can write [PooledTracePacket]s to. */
+public abstract class TraceSink : Closeable {
+    public abstract fun emit(pooledPacketArray: PooledTracePacketArray)
+
+    public abstract fun flush()
+}
+
+/** An empty trace sink that does nothing. */
+internal class EmptyTraceSink : TraceSink() {
+    override fun emit(pooledPacketArray: PooledTracePacketArray) {
+        // Does nothing
+    }
+
+    override fun flush() {
+        // Does nothing
+    }
+
+    override fun close() {
+        // Does nothing
+    }
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ThreadTrack.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ThreadTrack.kt
new file mode 100644
index 0000000..23e8fd0
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/ThreadTrack.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+/** Represents a track for a Thread like construct in Perfetto. */
+public open class ThreadTrack(
+    /** The thread id. */
+    internal val id: Int,
+    /** The name of the thread. */
+    internal val name: String,
+    /** The process track that the thread belongs to. */
+    internal val process: ProcessTrack,
+    hasPreamble: Boolean = true,
+) :
+    EventTrack(
+        context = process.context,
+        hasPreamble = hasPreamble,
+        uuid = monotonicId(),
+        parent = process
+    ) {
+    override fun preamblePacket(): PooledTracePacket? {
+        val packet = context.pool.obtainTracePacket()
+        val track = context.pool.obtainTrackDescriptor()
+        val thread = context.pool.obtainThreadDescriptor()
+        packet.trackPoolableForOwnership(track)
+        packet.trackPoolableForOwnership(thread)
+        // Populate thread details
+        thread.threadDescriptor.pid = process.id
+        thread.threadDescriptor.tid = id
+        thread.threadDescriptor.thread_name = name
+        // Link
+        track.trackDescriptor.uuid = uuid
+        track.trackDescriptor.thread = thread.threadDescriptor
+        packet.tracePacket.timestamp = nanoTime()
+        packet.tracePacket.track_descriptor = track.trackDescriptor
+        packet.tracePacket.trusted_packet_sequence_id = context.sequenceId
+        return packet
+    }
+}
+
+// An empty thread track when tracing is disabled
+
+private const val EMPTY_THREAD_ID = -1
+private const val EMPTY_THREAD_NAME = "Empty Thread"
+
+internal class EmptyThreadTrack(process: EmptyProcessTrack) :
+    ThreadTrack(
+        id = EMPTY_THREAD_ID,
+        name = EMPTY_THREAD_NAME,
+        process = process,
+        hasPreamble = false
+    ) {
+    override fun preamblePacket(): PooledTracePacket? = null
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/TraceContext.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/TraceContext.kt
new file mode 100644
index 0000000..99150de
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/TraceContext.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import androidx.collection.mutableIntObjectMapOf
+import okio.Closeable
+
+/**
+ * This is something that is only typically created once per process. All the traces emitted are
+ * managed and written into a single [TraceSink] in an optimal way based on the underlying platform.
+ */
+public open class TraceContext
+internal constructor(
+    public val sequenceId: Int,
+    /** The sink all the [PooledTracePacket]'s are written to. */
+    public val sink: TraceSink,
+    /** Is tracing enabled ? */
+    public val isEnabled: Boolean,
+    /** Debug mode */
+    // When debugging is on, we keep track of outstanding allocations in the pool,
+    // and provide useful logging to help with debugging & testing.
+    internal val isDebug: Boolean,
+) : Closeable {
+
+    public constructor(
+        sequenceId: Int,
+        sink: TraceSink,
+        isEnabled: Boolean
+    ) : this(sequenceId, sink, isEnabled, isDebug = false)
+
+    internal val lock = Lock()
+    internal val pool = ProtoPool(isDebug = isDebug)
+    internal val processes = mutableIntObjectMapOf<ProcessTrack>()
+
+    /** @return A [ProcessTrack] using the unique process [id] using the provided [TraceContext]. */
+    internal open fun ProcessTrack(id: Int, name: String): ProcessTrack {
+        val track = processes[id]
+        return track
+            ?: lock.withLock {
+                val track =
+                    processes.getOrPut(id) { ProcessTrack(context = this, id = id, name = name) }
+                check(track.name == name)
+                track
+            }
+    }
+
+    /** Flushes the trace packets into the underlying [TraceSink]. */
+    public fun flush() {
+        if (isEnabled) {
+            processes.forEachValue { processTrack ->
+                processTrack.flush()
+                processTrack.threads.forEachValue { threadTrack -> threadTrack.flush() }
+                processTrack.counters.forEachValue { counterTrack -> counterTrack.flush() }
+            }
+            // Call flush() on the sink after all the tracks have been flushed.
+            sink.flush()
+        }
+    }
+
+    override fun close() {
+        flush()
+        sink.close()
+    }
+}
+
+// An empty trace context when tracing is disabled.
+
+private const val EMPTY_TRACE_CONTEXT_SEQUENCE_ID = -1
+
+internal object EmptyTraceContext :
+    TraceContext(
+        sequenceId = EMPTY_TRACE_CONTEXT_SEQUENCE_ID,
+        sink = EmptyTraceSink(),
+        isEnabled = false
+    ) {
+    internal val process = EmptyProcessTrack(this)
+    internal val thread = EmptyThreadTrack(process)
+    internal val counter = EmptyCounterTrack(process)
+
+    override fun ProcessTrack(id: Int, name: String): ProcessTrack = process
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/TraceDriver.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/TraceDriver.kt
new file mode 100644
index 0000000..97a7491
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/TraceDriver.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+/** The entry point for the tracing API. */
+public class TraceDriver(
+    private val sequenceId: Int,
+    private val sink: TraceSink,
+    private val isEnabled: Boolean = true
+) {
+    public val context: TraceContext =
+        if (isEnabled) {
+            TraceContext(sequenceId = sequenceId, sink = sink, isEnabled = true)
+        } else {
+            EmptyTraceContext
+        }
+
+    /**
+     * @param id is the Process id.
+     * @param name is the name of the Process.
+     * @return a [ProcessTrack] instance that we can associate trace packets to.
+     */
+    public fun ProcessTrack(id: Int, name: String): ProcessTrack = context.ProcessTrack(id, name)
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Tracing.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Tracing.kt
new file mode 100644
index 0000000..d57dba2
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Tracing.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import perfetto.protos.MutableTrackEvent
+
+/** A [PooledTracePacket] that represents a starting point of a trace span. */
+internal fun <T : EventTrack> T.trackBeginPacket(
+    name: String,
+    flowIds: List<Long> = emptyList()
+): PooledTracePacket {
+    val packet = context.pool.obtainTracePacket()
+    val event = context.pool.obtainTrackEvent()
+    packet.trackPoolableForOwnership(event)
+    event.trackEvent.type = MutableTrackEvent.Type.TYPE_SLICE_BEGIN
+    event.trackEvent.track_uuid = uuid
+    event.trackEvent.name = name
+    // Wire creates an `immutableCopyOf(...) the incoming list.
+    // Only set the flowIds if they exist.
+    if (flowIds.isNotEmpty()) {
+        event.trackEvent.flow_ids = flowIds
+    }
+    packet.tracePacket.timestamp = nanoTime()
+    packet.tracePacket.track_event = event.trackEvent
+    packet.tracePacket.trusted_packet_sequence_id = context.sequenceId
+    return packet
+}
+
+/** A [PooledTracePacket] that represents an end point of a trace span. */
+internal fun <T : EventTrack> T.trackEndPacket(name: String): PooledTracePacket {
+    val packet = context.pool.obtainTracePacket()
+    val event = context.pool.obtainTrackEvent()
+    packet.trackPoolableForOwnership(event)
+    event.trackEvent.type = MutableTrackEvent.Type.TYPE_SLICE_END
+    event.trackEvent.track_uuid = uuid
+    event.trackEvent.name = name
+    packet.tracePacket.timestamp = nanoTime()
+    packet.tracePacket.track_event = event.trackEvent
+    packet.tracePacket.trusted_packet_sequence_id = context.sequenceId
+    return packet
+}
+
+internal fun <T : EventTrack> T.instantPacket(): PooledTracePacket {
+    val packet = context.pool.obtainTracePacket()
+    val event = context.pool.obtainTrackEvent()
+    packet.trackPoolableForOwnership(event)
+    event.trackEvent.type = MutableTrackEvent.Type.TYPE_INSTANT
+    event.trackEvent.track_uuid = uuid
+    packet.tracePacket.track_event = event.trackEvent
+    packet.tracePacket.timestamp = nanoTime()
+    packet.tracePacket.trusted_packet_sequence_id = context.sequenceId
+    return packet
+}
+
+internal fun CounterTrack.longCounterPacket(value: Long): PooledTracePacket {
+    val packet = context.pool.obtainTracePacket()
+    val event = context.pool.obtainTrackEvent()
+    packet.trackPoolableForOwnership(event)
+    event.trackEvent.type = MutableTrackEvent.Type.TYPE_COUNTER
+    event.trackEvent.track_uuid = uuid
+    event.trackEvent.counter_value = value
+    packet.tracePacket.track_event = event.trackEvent
+    packet.tracePacket.timestamp = nanoTime()
+    packet.tracePacket.trusted_packet_sequence_id = context.sequenceId
+    return packet
+}
+
+internal fun CounterTrack.doubleCounterPacket(value: Double): PooledTracePacket {
+    val packet = context.pool.obtainTracePacket()
+    val event = context.pool.obtainTrackEvent()
+    packet.trackPoolableForOwnership(event)
+    event.trackEvent.type = MutableTrackEvent.Type.TYPE_COUNTER
+    event.trackEvent.track_uuid = uuid
+    event.trackEvent.double_counter_value = value
+    packet.tracePacket.track_event = event.trackEvent
+    packet.tracePacket.timestamp = nanoTime()
+    packet.tracePacket.trusted_packet_sequence_id = context.sequenceId
+    return packet
+}
diff --git a/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Track.kt b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Track.kt
new file mode 100644
index 0000000..df2da31
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/kotlin/androidx/tracing/driver/Track.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import androidx.annotation.RestrictTo
+
+/** Entities that we can attach traces to. */
+public abstract class Track(
+    /** The [TraceContext] instance. */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public open val context: TraceContext,
+    /** `true` iff we need to emit some preamble packets. */
+    internal val hasPreamble: Boolean,
+    /** The uuid for the track descriptor. */
+    internal val uuid: Long,
+    /** The parent traceable. */
+    private val parent: Track?
+) {
+    /**
+     * Any time we emit trace packets relevant to this process. We need to make sure the necessary
+     * preamble packets that describe the process and threads are also emitted. This is used to make
+     * sure that we only do that once.
+     */
+    private val preamble: AtomicBoolean = AtomicBoolean(false)
+    private val flushRequested = AtomicBoolean(false)
+    private val queue: ArrayDeque<PooledTracePacket> = ArrayDeque(TRACE_PACKET_BUFFER_SIZE * 2)
+
+    /** @return The [PooledTracePacket] which is a preamble packet for the [Track]. */
+    public abstract fun preamblePacket(): PooledTracePacket?
+
+    internal fun emitPreamble() {
+        parent?.emitPreamble()
+        if (preamble.compareAndSet(expected = false, newValue = true)) {
+            val packet = preamblePacket()
+            if (packet != null) {
+                emit(packet)
+            }
+        }
+    }
+
+    internal fun flush() {
+        if (flushRequested.compareAndSet(expected = false, newValue = true)) {
+            transferPooledPacketArray()
+        }
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun emit(packet: PooledTracePacket) {
+        emitPreamble()
+        queue.add(packet)
+        if (queue.size >= TRACE_PACKET_BUFFER_SIZE) {
+            flush()
+        }
+    }
+
+    private fun transferPooledPacketArray() {
+        do {
+            val pooledPacketArray = context.pool.obtainTracePacketArray()
+            var i = 0
+            while (queue.isNotEmpty() && i < pooledPacketArray.pooledTracePacketArray.size) {
+                val pooledTracePacket = queue.removeFirstOrNull()
+                if (pooledTracePacket != null) {
+                    pooledPacketArray.pooledTracePacketArray[i] = pooledTracePacket
+                    i += 1
+                }
+            }
+            context.sink.emit(pooledPacketArray)
+        } while (queue.isNotEmpty()) // There might still be more packets.
+        flushRequested.set(newValue = false)
+    }
+}
diff --git a/tracing/tracing-driver/src/commonMain/proto/perfetto_trace.proto b/tracing/tracing-driver/src/commonMain/proto/perfetto_trace.proto
new file mode 100644
index 0000000..e7c8e72
--- /dev/null
+++ b/tracing/tracing-driver/src/commonMain/proto/perfetto_trace.proto
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+// NOTE: We get away with a copy of the proto definitions here only because in this module
+// we are using mutable types. Otherwise benchmark's definitions and this would collide and cause
+// builds to fail. Consider re-packaging this to a separate package in the future. b/392165540
+package perfetto.protos;
+
+///////////////////////////////////////////////////////////////////////////////
+// NOTE: THIS FILE IS A MANUALLY MINIFIED VERSION OF PERFETTO_TRACE.PROTO
+///////////////////////////////////////////////////////////////////////////////
+
+message EventName {
+  optional uint64 iid = 1;
+  optional string name = 2;
+}
+
+message InternedData {
+  repeated EventName event_names = 2;
+}
+
+message TracePacketDefaults {
+  optional uint32 timestamp_clock_id = 58;
+  optional TrackEventDefaults track_event_defaults = 11;
+}
+
+// A random unique ID that identifies the trace.
+// This message has been introduced in v32. Prior to that, the UUID was
+// only (optionally) present in the TraceConfig.trace_uuid_msb/lsb fields.
+message TraceUuid {
+  optional int64 msb = 1;
+  optional int64 lsb = 2;
+}
+
+message ProcessDescriptor {
+  required int32 pid = 1; // Making this required to avoid boxing an Int
+  repeated string cmdline = 2;
+  optional string process_name = 6;
+  // Labels can be used to further describe properties of the work performed by
+  // the process. For example, these can be used by Chrome renderer process to
+  // provide titles of frames being rendered.
+  repeated string process_labels = 8;
+}
+
+message TrackEvent {
+  repeated string categories = 22;
+  oneof name_field {
+    // interned EventName.
+    uint64 name_iid = 10;
+    // non-interned variant.
+    string name = 23;
+  }
+  enum Type {
+    TYPE_SLICE_BEGIN = 1;
+    TYPE_SLICE_END = 2;
+    TYPE_INSTANT = 3;
+    TYPE_COUNTER = 4;
+  }
+  optional Type type = 9;
+  optional uint64 track_uuid = 11;
+  oneof counter_value_field {
+    int64 counter_value = 30;
+    double double_counter_value = 44;
+  }
+  repeated uint64 extra_counter_track_uuids = 31;
+  repeated int64 extra_counter_values = 12;
+  repeated uint64 extra_double_counter_track_uuids = 45;
+  repeated double extra_double_counter_values = 46;
+  repeated fixed64 flow_ids = 47;
+}
+
+message ThreadDescriptor {
+  required int32 pid = 1; // Making this required to avoid boxing an Int
+  required int32 tid = 2; // Making this required to avoid boxing an Int
+  optional string thread_name = 5;
+}
+
+message CounterDescriptor {
+  // Built-in counters, usually with special meaning in the client library,
+  // trace processor, legacy JSON format, or UI. Trace processor will infer a
+  // track name from the enum value if none is provided in TrackDescriptor.
+  enum BuiltinCounterType {
+    COUNTER_UNSPECIFIED = 0;
+
+    // Thread-scoped counters. The thread's track should be specified via
+    // |parent_uuid| in the TrackDescriptor for such a counter.
+
+    // implies UNIT_TIME_NS.
+    COUNTER_THREAD_TIME_NS = 1;
+
+    // implies UNIT_COUNT.
+    COUNTER_THREAD_INSTRUCTION_COUNT = 2;
+  }
+
+  // Type of the values for the counters - to supply lower granularity units,
+  // see also |unit_multiplier|.
+  enum Unit {
+    UNIT_UNSPECIFIED = 0;
+    UNIT_TIME_NS = 1;
+    UNIT_COUNT = 2;
+    UNIT_SIZE_BYTES = 3;
+  }
+
+  // For built-in counters (e.g. thread time). Custom user-specified counters
+  // (e.g. those emitted by TRACE_COUNTER macros of the client library)
+  // shouldn't set this, and instead provide a counter name via TrackDescriptor.
+  optional BuiltinCounterType type = 1;
+
+  // Names of categories of the counter (usually for user-specified counters).
+  // In the client library, categories are a way to turn groups of individual
+  // counters (or events) on or off.
+  repeated string categories = 2;
+
+  // Type of the counter's values. Built-in counters imply a value for this
+  // field.
+  optional Unit unit = 3;
+
+  // In order to use a unit not defined as a part of |Unit|, a free-form unit
+  // name can be used instead.
+  optional string unit_name = 6;
+
+  // Multiplication factor of this counter's values, e.g. to supply
+  // COUNTER_THREAD_TIME_NS timestamps in microseconds instead.
+  optional int64 unit_multiplier = 4;
+
+  // Whether values for this counter are provided as delta values. Only
+  // supported for counters that are emitted on a single packet-sequence (e.g.
+  // thread time). Counter values in subsequent packets on the current packet
+  // sequence will be interpreted as delta values from the sequence's most
+  // recent value for the counter. When incremental state is cleared, the
+  // counter value is considered to be reset to 0. Thus, the first value after
+  // incremental state is cleared is effectively an absolute value.
+  optional bool is_incremental = 5;
+}
+
+message TrackDescriptor {
+  optional uint64 uuid = 1;
+  optional string name = 2;
+  optional uint64 parent_uuid = 5;
+  optional ProcessDescriptor process = 3;
+  optional ThreadDescriptor thread = 4;
+  optional CounterDescriptor counter = 8;
+  optional bool disallow_merging_with_system_tracks = 9;
+}
+
+message TrackEventDefaults {
+  optional uint64 track_uuid = 11;
+}
+
+message TracePacket {
+  required uint64 timestamp = 8; // Making this required to avoid boxing
+  optional uint32 timestamp_clock_id = 58;
+  oneof data {
+    TrackEvent track_event = 11;
+    TrackDescriptor track_descriptor = 60;
+    TraceUuid trace_uuid = 89;
+    bytes compressed_packets = 50; // kept for testing purposes
+  }
+  optional uint32 trusted_packet_sequence_id = 10;
+  optional InternedData interned_data = 12;
+
+  enum SequenceFlags {
+    SEQ_UNSPECIFIED = 0;
+    SEQ_INCREMENTAL_STATE_CLEARED = 1;
+    SEQ_NEEDS_INCREMENTAL_STATE = 2;
+  };
+  optional uint32 sequence_flags = 13;
+  optional bool incremental_state_cleared = 41;
+  optional TracePacketDefaults trace_packet_defaults = 59;
+}
+
+message Trace {
+  repeated TracePacket packet = 1;
+}
diff --git a/tracing/tracing-driver/src/commonTest/kotlin/androidx/tracing/driver/Sinks.kt b/tracing/tracing-driver/src/commonTest/kotlin/androidx/tracing/driver/Sinks.kt
new file mode 100644
index 0000000..6090ccd
--- /dev/null
+++ b/tracing/tracing-driver/src/commonTest/kotlin/androidx/tracing/driver/Sinks.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+/** A sink that does very little. We simply drop the trace packets without writing it to a file. */
+class NoOpSink : TraceSink() {
+    override fun emit(packetArray: PooledTracePacketArray) {
+        for (packet in packetArray.pooledTracePacketArray) {
+            packet?.recycle()
+        }
+        packetArray.recycle()
+    }
+
+    override fun flush() {
+        // Does nothing
+    }
+
+    override fun close() {
+        // Does nothing
+    }
+}
diff --git a/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/AtomicBoolean.jvm.kt b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/AtomicBoolean.jvm.kt
new file mode 100644
index 0000000..9446267
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/AtomicBoolean.jvm.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+public actual typealias AtomicBoolean = AtomicBoolean
diff --git a/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/AtomicLong.jvm.kt b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/AtomicLong.jvm.kt
new file mode 100644
index 0000000..60db600
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/AtomicLong.jvm.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import java.util.concurrent.atomic.AtomicLong
+
+public actual typealias AtomicLong = AtomicLong
diff --git a/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/ClockSource.jvm.kt b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/ClockSource.jvm.kt
new file mode 100644
index 0000000..f6763fb
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/ClockSource.jvm.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+@Suppress("NOTHING_TO_INLINE") public actual inline fun nanoTime(): Long = System.nanoTime()
diff --git a/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/Lock.jvm.kt b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/Lock.jvm.kt
new file mode 100644
index 0000000..93ccff1
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/Lock.jvm.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.tracing.driver
+
+import java.util.concurrent.locks.ReentrantLock
+
+public actual typealias Lock = ReentrantLock
diff --git a/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/TraceSink.jvm.kt b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/TraceSink.jvm.kt
new file mode 100644
index 0000000..235c90c
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmMain/kotlin/androidx/tracing/driver/TraceSink.jvm.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import com.squareup.wire.ProtoWriter
+import java.io.Closeable
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.coroutineContext
+import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import okio.BufferedSink
+import okio.appendingSink
+import okio.buffer
+
+/** The trace sink that writes to a new file per trace session. */
+public class JvmTraceSink(
+    @Suppress("UNUSED") private val baseDir: File,
+    private val coroutineContext: CoroutineContext = Dispatchers.IO
+) : TraceSink(), Closeable {
+    internal val traceFile = baseDir.perfettoTraceFile()
+    private val bufferedSink: BufferedSink = traceFile.appendingSink().buffer()
+    private val protoWriter: ProtoWriter = ProtoWriter(bufferedSink)
+
+    // There are 2 distinct mechanisms for thread safety here, and they are not necessarily in sync.
+    // The Queue by itself is thread-safe, but after we drain the queue we mark drainRequested
+    // to false (not an atomic operation). So a writer can come along and add a pooled array of
+    // trace packets. That is still okay given, those packets will get picked during the next
+    // drain request; or on flush() prior to the close() of the Sink.
+    // No packets are lost or dropped; and therefore we are still okay with this small
+    // compromise with thread safety.
+    private val queue = Queue<PooledTracePacketArray>()
+    private val drainRequested = AtomicBoolean(false)
+
+    @Volatile private var resumeDrain: Continuation<Unit>
+
+    init {
+        resumeDrain =
+            suspend {
+                    coroutineContext[Job]?.invokeOnCompletion { makeDrainRequest() }
+                    while (true) {
+                        drainQueue() // Sets drainRequested to false on completion
+                        suspendCoroutine<Unit> { continuation ->
+                            resumeDrain = continuation
+                            COROUTINE_SUSPENDED // Suspend
+                        }
+                    }
+                }
+                .createCoroutineUnintercepted(Continuation(context = coroutineContext) {})
+
+        // Kick things off and suspend
+        makeDrainRequest()
+    }
+
+    override fun emit(packetArray: PooledTracePacketArray) {
+        queue.addFirst(packetArray)
+        makeDrainRequest()
+    }
+
+    override fun flush() {
+        makeDrainRequest()
+        while (drainRequested.get()) {
+            // Await completion of the drain.
+        }
+        bufferedSink.flush()
+    }
+
+    private fun makeDrainRequest() {
+        // Only make a request if one is not already ongoing
+        if (drainRequested.compareAndSet(false, true)) {
+            resumeDrain.resume(Unit)
+        }
+    }
+
+    private fun drainQueue() {
+        while (queue.isNotEmpty()) {
+            val pooledPacketArray = queue.removeFirstOrNull()
+            if (pooledPacketArray != null) {
+                for (pooledTracePacket in pooledPacketArray.pooledTracePacketArray) {
+                    if (pooledTracePacket != null) {
+                        pooledTracePacket.encodeTracePacket(protoWriter)
+                        pooledTracePacket.recycle()
+                    }
+                }
+                pooledPacketArray.recycle()
+            }
+        }
+        drainRequested.set(false)
+    }
+
+    override fun close() {
+        makeDrainRequest()
+        bufferedSink.close()
+    }
+}
+
+private fun File.perfettoTraceFile(): File {
+    val formatter = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
+    formatter.timeZone = TimeZone.getTimeZone("UTC")
+    val traceFile = File(this, "perfetto-${formatter.format(Date())}.perfetto-trace")
+    return traceFile
+}
diff --git a/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/Asserts.kt b/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/Asserts.kt
new file mode 100644
index 0000000..c6c4e8ee
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/Asserts.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import kotlin.test.assertNotNull
+import perfetto.protos.MutableTrackEvent
+
+internal fun List<PooledTracePacket>.trackEventPacket(
+    name: String,
+    type: MutableTrackEvent.Type? = null
+): PooledTracePacket? {
+    return find { packet ->
+        var result = packet.tracePacket.track_event?.name == name
+        if (type != null) {
+            val sameType = type == packet.tracePacket.track_event?.type
+            result = result and sameType
+        }
+        result
+    }
+}
+
+internal fun List<PooledTracePacket>.assertTraceSection(name: String) {
+    val begin = trackEventPacket(name = name, type = MutableTrackEvent.Type.TYPE_SLICE_BEGIN)
+    val end = trackEventPacket(name = name, type = MutableTrackEvent.Type.TYPE_SLICE_END)
+    assertNotNull(begin) {
+        "Cannot find a track event of type ${MutableTrackEvent.Type.TYPE_SLICE_BEGIN} for $name"
+    }
+    assertNotNull(end) {
+        "Cannot find a track event of type ${MutableTrackEvent.Type.TYPE_SLICE_END} for $name"
+    }
+}
diff --git a/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/RecyclingTest.kt b/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/RecyclingTest.kt
new file mode 100644
index 0000000..15a6030
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/RecyclingTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class RecyclingTest {
+    private val sink = NoOpSink()
+
+    private val context: TraceContext =
+        TraceContext(sequenceId = 1, sink = sink, isEnabled = true, isDebug = true)
+
+    @Test
+    internal fun testProcessTrackEvents() {
+        context.use {
+            val process = context.ProcessTrack(id = 1, name = "process")
+            val thread = process.ThreadTrack(1, "thread")
+            thread.trace("section") {}
+        }
+        assertTrue(context.isDebug)
+        assertEquals(0, context.pool.poolableCount())
+    }
+
+    @Test
+    internal fun testProcessTrackFlows() = runTest {
+        context.use {
+            val process = context.ProcessTrack(id = 1, name = "process")
+            val thread = process.ThreadTrack(1, "thread")
+            thread.traceFlow("section") {}
+        }
+        assertTrue(context.isDebug)
+        assertEquals(0, context.pool.poolableCount())
+    }
+}
diff --git a/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/TracingDemoTest.kt b/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/TracingDemoTest.kt
new file mode 100644
index 0000000..a1ac54c
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/TracingDemoTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import java.io.File
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+
+class TracingDemoTest {
+
+    internal val random = Random(42)
+
+    internal val forkSize = 20
+    internal val multiplier = 1000
+    internal val inputSize = forkSize * multiplier
+
+    // Tracks the number of batches completed
+    internal var count = 0L
+    internal val driver: TraceDriver =
+        TraceDriver(sequenceId = 1, sink = JvmTraceSink(File("/tmp")), isEnabled = true)
+
+    @Test
+    internal fun testTracingEndToEnd() = runBlocking {
+        driver.context.use {
+            withContext(context = Dispatchers.Default) {
+                // Create a process track
+                val track = driver.ProcessTrack(id = 1, name = "TracingTest")
+                track.traceFlow("begin") { delay(20L) }
+                track.traceFlow("histograms-end-to-end") { track.computeHistograms() }
+                track.traceFlow("end") { delay(20L) }
+            }
+        }
+    }
+
+    internal suspend fun ProcessTrack.computeHistograms(): Map<Int, Int> {
+        val input = List<Int>(inputSize) { random.nextInt(0, 100_000) }
+        val batches = input.chunked(multiplier)
+        return coroutineScope {
+            val jobs = mutableListOf<Deferred<Map<Int, Int>>>()
+            batches.forEachIndexed { index, batch ->
+                jobs += async { traceFlow("histograms-batch-$index") { computeHistogram(batch) } }
+            }
+            val histograms = jobs.awaitAll()
+            val output = traceFlow("merge-histograms") { mergeHistograms(input, histograms) }
+            output
+        }
+    }
+
+    internal suspend fun ProcessTrack.computeHistogram(list: List<Int>): Map<Int, Int> {
+        val counter = CounterTrack("Batches Completed")
+        val frequency = mutableMapOf<Int, Int>()
+        for (element in list) {
+            val count = frequency[element] ?: 0
+            frequency[element] = count + 1
+        }
+        delay(random.nextLong(10L, 20L)) // Waterfall
+        count += 1
+        counter.emitLongCounterPacket(count)
+        return frequency
+    }
+
+    internal suspend fun ProcessTrack.mergeHistograms(
+        input: List<Int>,
+        histograms: List<Map<Int, Int>>,
+    ): Map<Int, Int> {
+        val frequency = mutableMapOf<Int, Int>()
+        for (element in input) {
+            var count = 0
+            for (histogram in histograms) {
+                count += histogram[element] ?: 0
+            }
+            frequency[element] = count
+        }
+        delay(random.nextLong(10L, 20L)) // Waterfall
+        return frequency
+    }
+}
diff --git a/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/TracingTest.kt b/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/TracingTest.kt
new file mode 100644
index 0000000..3b5ea4b
--- /dev/null
+++ b/tracing/tracing-driver/src/jvmTest/kotlin/androidx/tracing/driver/TracingTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2025 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.
+ */
+
+package androidx.tracing.driver
+
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertContains
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.test.runTest
+
+class TestSink : TraceSink() {
+    internal val packets = mutableListOf<PooledTracePacket>()
+
+    override fun emit(packetArray: PooledTracePacketArray) {
+        for (packet in packetArray.pooledTracePacketArray) {
+            if (packet != null) {
+                packets += packet
+            }
+        }
+        packetArray.recycle()
+    }
+
+    override fun flush() {
+        // Does nothing
+    }
+
+    override fun close() {
+        // Does nothing
+    }
+}
+
+class TracingTest {
+    private val sink = TestSink()
+    private val context: TraceContext = TraceContext(sequenceId = 1, sink = sink, isEnabled = true)
+
+    @BeforeTest
+    fun setUp() {
+        for (packet in sink.packets) {
+            packet.recycle()
+        }
+        sink.packets.clear()
+    }
+
+    @Test
+    internal fun testProcessTrackEvents() {
+        context.use {
+            val process = context.ProcessTrack(id = 1, name = "process")
+            val thread = process.ThreadTrack(1, "thread")
+            thread.trace("section") {}
+        }
+        assertTrue(sink.packets.size == 4)
+        assertNotNull(
+            sink.packets.find {
+                it.tracePacket.track_descriptor?.process?.process_name == "process"
+            }
+        )
+        assertNotNull(
+            sink.packets.find { it.tracePacket.track_descriptor?.thread?.thread_name == "thread" }
+        )
+        sink.packets.assertTraceSection("section")
+    }
+
+    @Test
+    internal fun testCounterTrackEvents() {
+        context.use {
+            val process = context.ProcessTrack(id = 1, name = "process")
+            val counter = process.CounterTrack("counter")
+            counter.emitLongCounterPacket(10L)
+        }
+        assertTrue(sink.packets.size == 3)
+    }
+
+    @Test
+    internal fun testAsyncEventsInProcess() {
+        context.use {
+            val process = context.ProcessTrack(id = 1, name = "process")
+            process.trace("section") {}
+            process.trace("section2") {}
+        }
+        assertTrue(sink.packets.size == 5)
+        assertNotNull(
+            sink.packets.find {
+                it.tracePacket.track_descriptor?.process?.process_name == "process"
+            }
+        )
+        sink.packets.assertTraceSection("section")
+        sink.packets.assertTraceSection("section2")
+    }
+
+    @Test
+    internal fun testAsyncEventsWithFlows() = runTest {
+        context.use {
+            with(context) {
+                val process = ProcessTrack(id = 1, name = "process")
+                with(process) {
+                    traceFlow("service") {
+                        coroutineScope {
+                            async { traceFlow(name = "method1") { delay(10) } }.await()
+                            async { traceFlow(name = "method2") { delay(40) } }.await()
+                        }
+                    }
+                }
+            }
+        }
+        assertTrue { sink.packets.isNotEmpty() }
+        val serviceBegin = sink.packets.trackEventPacket(name = "service")
+        val method1Begin = sink.packets.trackEventPacket(name = "method1")
+        val method2Begin = sink.packets.trackEventPacket(name = "method2")
+        assertNotNull(serviceBegin) { "Cannot find packet with name service" }
+        val flowId = serviceBegin.tracePacket.track_event?.flow_ids?.first()
+        assertNotNull(flowId) { "Packet $serviceBegin does not include a flow_id" }
+        assertNotNull(method1Begin) { "Cannot find packet with name method1" }
+        assertNotNull(method2Begin) { "Cannot find packet with name method2" }
+        assertContains(method1Begin.tracePacket.track_event?.flow_ids ?: emptyList(), flowId)
+        assertContains(method2Begin.tracePacket.track_event?.flow_ids ?: emptyList(), flowId)
+    }
+}