Expose Perfetto DataSource to java
Bug: 309630341
Test: atest CoreTracingTests
Change-Id: I9e9486ba406aa67fbc73922910ea97429ee4683c
diff --git a/Android.bp b/Android.bp
index f3b2ebb..dde94a1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -149,6 +149,7 @@
":framework-javastream-protos",
":statslog-framework-java-gen", // FrameworkStatsLog.java
":audio_policy_configuration_V7_0",
+ ":perfetto_trace_javastream_protos",
],
}
diff --git a/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java
new file mode 100644
index 0000000..819cc8c
--- /dev/null
+++ b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ * @param <DataSourceInstanceType> The type of datasource instance this state applied to.
+ */
+public class CreateIncrementalStateArgs<DataSourceInstanceType extends DataSourceInstance> {
+ private final DataSource<DataSourceInstanceType, Object, Object> mDataSource;
+ private final int mInstanceIndex;
+
+ CreateIncrementalStateArgs(DataSource dataSource, int instanceIndex) {
+ this.mDataSource = dataSource;
+ this.mInstanceIndex = instanceIndex;
+ }
+
+ /**
+ * Gets the datasource instance for this state with a lock.
+ * releaseDataSourceInstanceLocked must be called before this can be called again.
+ * @return The data source instance for this state.
+ * Null if the datasource instance no longer exists.
+ */
+ public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() {
+ return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
+ }
+}
diff --git a/core/java/android/tracing/perfetto/CreateTlsStateArgs.java b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java
new file mode 100644
index 0000000..3fad2d1
--- /dev/null
+++ b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ * @param <DataSourceInstanceType> The type of datasource instance this state applied to.
+ */
+public class CreateTlsStateArgs<DataSourceInstanceType extends DataSourceInstance> {
+ private final DataSource<DataSourceInstanceType, Object, Object> mDataSource;
+ private final int mInstanceIndex;
+
+ CreateTlsStateArgs(DataSource dataSource, int instanceIndex) {
+ this.mDataSource = dataSource;
+ this.mInstanceIndex = instanceIndex;
+ }
+
+ /**
+ * Gets the datasource instance for this state with a lock.
+ * releaseDataSourceInstanceLocked must be called before this can be called again.
+ * @return The data source instance for this state.
+ * Null if the datasource instance no longer exists.
+ */
+ public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() {
+ return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
+ }
+}
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
new file mode 100644
index 0000000..4e08aee
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+/**
+ * Templated base class meant to be derived by embedders to create a custom data
+ * source.
+ *
+ * @param <DataSourceInstanceType> The type for the DataSource instances that will be created from
+ * this DataSource type.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public abstract class DataSource<DataSourceInstanceType extends DataSourceInstance,
+ TlsStateType, IncrementalStateType> {
+ protected final long mNativeObj;
+
+ public final String name;
+
+ /**
+ * A function implemented by each datasource to create a new data source instance.
+ *
+ * @param configStream A ProtoInputStream to read the tracing instance's config.
+ * @return A new data source instance setup with the provided config.
+ */
+ public abstract DataSourceInstanceType createInstance(
+ ProtoInputStream configStream, int instanceIndex);
+
+ /**
+ * Constructor for datasource base class.
+ *
+ * @param name The fully qualified name of the datasource.
+ */
+ public DataSource(String name) {
+ this.name = name;
+ this.mNativeObj = nativeCreate(this, name);
+ }
+
+ /**
+ * The main tracing method. Tracing code should call this passing a lambda as
+ * argument, with the following signature: void(TraceContext).
+ * <p>
+ * The lambda will be called synchronously (i.e., always before trace()
+ * returns) only if tracing is enabled and the data source has been enabled in
+ * the tracing config.
+ * <p>
+ * The lambda can be called more than once per trace() call, in the case of
+ * concurrent tracing sessions (or even if the data source is instantiated
+ * twice within the same trace config).
+ *
+ * @param fun The tracing lambda that will be called with the tracing contexts of each active
+ * tracing instance.
+ */
+ public final void trace(
+ TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) {
+ nativeTrace(mNativeObj, fun);
+ }
+
+ /**
+ * Flush any trace data from this datasource that has not yet been flushed.
+ */
+ public final void flush() {
+ nativeFlushAll(mNativeObj);
+ }
+
+ /**
+ * Override this method to create a custom TlsState object for your DataSource. A new instance
+ * will be created per trace instance per thread.
+ *
+ * NOTE: Should only be called from native side.
+ */
+ protected TlsStateType createTlsState(CreateTlsStateArgs<DataSourceInstanceType> args) {
+ return null;
+ }
+
+ /**
+ * Override this method to create and use a custom IncrementalState object for your DataSource.
+ *
+ * NOTE: Should only be called from native side.
+ */
+ protected IncrementalStateType createIncrementalState(
+ CreateIncrementalStateArgs<DataSourceInstanceType> args) {
+ return null;
+ }
+
+ /**
+ * Registers the data source on all tracing backends, including ones that
+ * connect after the registration. Doing so enables the data source to receive
+ * Setup/Start/Stop notifications and makes the trace() method work when
+ * tracing is enabled and the data source is selected.
+ * <p>
+ * NOTE: Once registered, we cannot unregister the data source. Therefore, we should avoid
+ * creating and registering data source where not strictly required. This is a fundamental
+ * limitation of Perfetto itself.
+ *
+ * @param params Params to initialize the datasource with.
+ */
+ public void register(DataSourceParams params) {
+ nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy);
+ }
+
+ /**
+ * Gets the datasource instance with a specified index.
+ * IMPORTANT: releaseDataSourceInstance must be called after using the datasource instance.
+ * @param instanceIndex The index of the datasource to lock and get.
+ * @return The DataSourceInstance at index instanceIndex.
+ * Null if the datasource instance at the requested index doesn't exist.
+ */
+ public DataSourceInstanceType getDataSourceInstanceLocked(int instanceIndex) {
+ return (DataSourceInstanceType) nativeGetPerfettoInstanceLocked(mNativeObj, instanceIndex);
+ }
+
+ /**
+ * Unlock the datasource at the specified index.
+ * @param instanceIndex The index of the datasource to unlock.
+ */
+ protected void releaseDataSourceInstance(int instanceIndex) {
+ nativeReleasePerfettoInstanceLocked(mNativeObj, instanceIndex);
+ }
+
+ /**
+ * Called from native side when a new tracing instance starts.
+ *
+ * @param rawConfig byte array of the PerfettoConfig encoded proto.
+ * @return A new Java DataSourceInstance object.
+ */
+ private DataSourceInstanceType createInstance(byte[] rawConfig, int instanceIndex) {
+ final ProtoInputStream inputStream = new ProtoInputStream(rawConfig);
+ return this.createInstance(inputStream, instanceIndex);
+ }
+
+ private static native void nativeRegisterDataSource(
+ long dataSourcePtr, int bufferExhaustedPolicy);
+
+ private static native long nativeCreate(DataSource thiz, String name);
+ private static native void nativeTrace(
+ long nativeDataSourcePointer, TraceFunction traceFunction);
+ private static native void nativeFlushAll(long nativeDataSourcePointer);
+ private static native long nativeGetFinalizer();
+
+ private static native DataSourceInstance nativeGetPerfettoInstanceLocked(
+ long dataSourcePtr, int dsInstanceIdx);
+ private static native void nativeReleasePerfettoInstanceLocked(
+ long dataSourcePtr, int dsInstanceIdx);
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceInstance.java b/core/java/android/tracing/perfetto/DataSourceInstance.java
new file mode 100644
index 0000000..4994501
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSourceInstance.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public abstract class DataSourceInstance implements AutoCloseable {
+ private final DataSource mDataSource;
+ private final int mInstanceIndex;
+
+ public DataSourceInstance(DataSource dataSource, int instanceIndex) {
+ this.mDataSource = dataSource;
+ this.mInstanceIndex = instanceIndex;
+ }
+
+ /**
+ * Executed when the tracing instance starts running.
+ * <p>
+ * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+ * Anything that is run in this callback should execute quickly.
+ *
+ * @param args Start arguments.
+ */
+ protected void onStart(StartCallbackArguments args) {}
+
+ /**
+ * Executed when a flush is triggered.
+ * <p>
+ * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+ * Anything that is run in this callback should execute quickly.
+ * @param args Flush arguments.
+ */
+ protected void onFlush(FlushCallbackArguments args) {}
+
+ /**
+ * Executed when the tracing instance is stopped.
+ * <p>
+ * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+ * Anything that is run in this callback should execute quickly.
+ * @param args Stop arguments.
+ */
+ protected void onStop(StopCallbackArguments args) {}
+
+ @Override
+ public final void close() {
+ this.release();
+ }
+
+ /**
+ * Release the lock on the datasource once you are finished using it.
+ * Only required to be called when instance was retrieved with
+ * `DataSource#getDataSourceInstanceLocked`.
+ */
+ public final void release() {
+ mDataSource.releaseDataSourceInstance(mInstanceIndex);
+ }
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java
new file mode 100644
index 0000000..6cd04e3
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSourceParams.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DataSource Parameters
+ *
+ * @hide
+ */
+public class DataSourceParams {
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP,
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PerfettoDsBufferExhausted {}
+
+ // If the data source runs out of space when trying to acquire a new chunk,
+ // it will drop data.
+ public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP = 0;
+
+ // If the data source runs out of space when trying to acquire a new chunk,
+ // it will stall, retry and eventually abort if a free chunk is not acquired
+ // after a while.
+ public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1;
+
+ public static DataSourceParams DEFAULTS =
+ new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP);
+
+ public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) {
+ this.bufferExhaustedPolicy = bufferExhaustedPolicy;
+ }
+
+ public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy;
+}
diff --git a/core/java/android/tracing/perfetto/FlushCallbackArguments.java b/core/java/android/tracing/perfetto/FlushCallbackArguments.java
new file mode 100644
index 0000000..ecf6aee
--- /dev/null
+++ b/core/java/android/tracing/perfetto/FlushCallbackArguments.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class FlushCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/InitArguments.java b/core/java/android/tracing/perfetto/InitArguments.java
new file mode 100644
index 0000000..da8c273
--- /dev/null
+++ b/core/java/android/tracing/perfetto/InitArguments.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public class InitArguments {
+ public final @PerfettoBackend int backends;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ PERFETTO_BACKEND_IN_PROCESS,
+ PERFETTO_BACKEND_SYSTEM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PerfettoBackend {}
+
+ // The in-process tracing backend. Keeps trace buffers in the process memory.
+ public static final int PERFETTO_BACKEND_IN_PROCESS = (1 << 0);
+
+ // The system tracing backend. Connects to the system tracing service (e.g.
+ // on Linux/Android/Mac uses a named UNIX socket).
+ public static final int PERFETTO_BACKEND_SYSTEM = (1 << 1);
+
+ public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM);
+
+ public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS);
+
+ public InitArguments(@PerfettoBackend int backends) {
+ this.backends = backends;
+ }
+}
diff --git a/core/java/android/tracing/perfetto/Producer.java b/core/java/android/tracing/perfetto/Producer.java
new file mode 100644
index 0000000..a1b3eb7
--- /dev/null
+++ b/core/java/android/tracing/perfetto/Producer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class Producer {
+
+ /**
+ * Initializes the global Perfetto producer.
+ *
+ * @param args arguments on how to initialize the Perfetto producer.
+ */
+ public static void init(InitArguments args) {
+ nativePerfettoProducerInit(args.backends);
+ }
+
+ private static native void nativePerfettoProducerInit(int backends);
+}
diff --git a/core/java/android/tracing/perfetto/StartCallbackArguments.java b/core/java/android/tracing/perfetto/StartCallbackArguments.java
new file mode 100644
index 0000000..9739d27
--- /dev/null
+++ b/core/java/android/tracing/perfetto/StartCallbackArguments.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class StartCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/StopCallbackArguments.java b/core/java/android/tracing/perfetto/StopCallbackArguments.java
new file mode 100644
index 0000000..0cd1a18
--- /dev/null
+++ b/core/java/android/tracing/perfetto/StopCallbackArguments.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class StopCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/TraceFunction.java b/core/java/android/tracing/perfetto/TraceFunction.java
new file mode 100644
index 0000000..62941df
--- /dev/null
+++ b/core/java/android/tracing/perfetto/TraceFunction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import java.io.IOException;
+
+/**
+ * The interface for the trace function called from native on a trace call with a context.
+ *
+ * @param <DataSourceInstanceType> The type of DataSource this tracing context is for.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance,
+ TlsStateType, IncrementalStateType> {
+
+ /**
+ * This function will be called synchronously (i.e., always before trace() returns) only if
+ * tracing is enabled and the data source has been enabled in the tracing config.
+ * It can be called more than once per trace() call, in the case of concurrent tracing sessions
+ * (or even if the data source is instantiated twice within the same trace config).
+ *
+ * @param ctx the tracing context to trace for in the trace function.
+ */
+ void trace(TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx)
+ throws IOException;
+}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
new file mode 100644
index 0000000..0bce26e
--- /dev/null
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import android.util.proto.ProtoOutputStream;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Argument passed to the lambda function passed to Trace().
+ *
+ * @param <DataSourceInstanceType> The type of the datasource this tracing context is for.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
+ TlsStateType, IncrementalStateType> {
+
+ private final long mContextPtr;
+ private final TlsStateType mTlsState;
+ private final IncrementalStateType mIncrementalState;
+ private final List<ProtoOutputStream> mTracePackets = new ArrayList<>();
+
+ // Should only be created from the native side.
+ private TracingContext(long contextPtr, TlsStateType tlsState,
+ IncrementalStateType incrementalState) {
+ this.mContextPtr = contextPtr;
+ this.mTlsState = tlsState;
+ this.mIncrementalState = incrementalState;
+ }
+
+ /**
+ * Creates a new output stream to be used to write a trace packet to. The output stream will be
+ * encoded to the proto binary representation when the callback trace function finishes and
+ * send over to the native side to be included in the proto buffer.
+ *
+ * @return A proto output stream to write a trace packet proto to
+ */
+ public ProtoOutputStream newTracePacket() {
+ final ProtoOutputStream os = new ProtoOutputStream(0);
+ mTracePackets.add(os);
+ return os;
+ }
+
+ /**
+ * Forces a commit of the thread-local tracing data written so far to the
+ * service. This is almost never required (tracing data is periodically
+ * committed as trace pages are filled up) and has a non-negligible
+ * performance hit (requires an IPC + refresh of the current thread-local
+ * chunk). The only case when this should be used is when handling OnStop()
+ * asynchronously, to ensure sure that the data is committed before the
+ * Stop timeout expires.
+ */
+ public void flush() {
+ nativeFlush(this, mContextPtr);
+ }
+
+ /**
+ * Can optionally be used to store custom per-sequence
+ * session data, which is not reset when incremental state is cleared
+ * (e.g. configuration options).
+ *
+ * @return The TlsState instance for the tracing thread and instance.
+ */
+ public TlsStateType getCustomTlsState() {
+ return this.mTlsState;
+ }
+
+ /**
+ * Can optionally be used store custom per-sequence
+ * incremental data (e.g., interning tables).
+ *
+ * @return The current IncrementalState object instance.
+ */
+ public IncrementalStateType getIncrementalState() {
+ return this.mIncrementalState;
+ }
+
+ // Called from native to get trace packets
+ private byte[][] getAndClearAllPendingTracePackets() {
+ byte[][] res = new byte[mTracePackets.size()][];
+ for (int i = 0; i < mTracePackets.size(); i++) {
+ ProtoOutputStream tracePacket = mTracePackets.get(i);
+ res[i] = tracePacket.getBytes();
+ }
+
+ mTracePackets.clear();
+ return res;
+ }
+
+ // private static native void nativeFlush(long nativeDataSourcePointer);
+ private static native void nativeFlush(TracingContext thiz, long ctxPointer);
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2a744e3..c8fd246 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -269,6 +269,9 @@
"android_window_WindowInfosListener.cpp",
"android_window_ScreenCapture.cpp",
"jni_common.cpp",
+ "android_tracing_PerfettoDataSource.cpp",
+ "android_tracing_PerfettoDataSourceInstance.cpp",
+ "android_tracing_PerfettoProducer.cpp",
],
static_libs: [
@@ -282,6 +285,7 @@
"libscrypt_static",
"libstatssocket_lazy",
"libskia",
+ "libperfetto_client_experimental",
],
shared_libs: [
@@ -355,6 +359,7 @@
"server_configurable_flags",
"libimage_io",
"libultrahdr",
+ "libperfetto_c",
],
export_shared_lib_headers: [
// our headers include libnativewindow's public headers
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 17aad43..7a16318 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -220,6 +220,9 @@
extern int register_android_window_WindowInfosListener(JNIEnv* env);
extern int register_android_window_ScreenCapture(JNIEnv* env);
extern int register_jni_common(JNIEnv* env);
+extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
+extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
+extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
// Namespace for Android Runtime flags applied during boot time.
static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1675,6 +1678,10 @@
REG_JNI(register_android_window_WindowInfosListener),
REG_JNI(register_android_window_ScreenCapture),
REG_JNI(register_jni_common),
+
+ REG_JNI(register_android_tracing_PerfettoDataSource),
+ REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
+ REG_JNI(register_android_tracing_PerfettoProducer),
};
/*
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
new file mode 100644
index 0000000..d710698
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include "android_tracing_PerfettoDataSource.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jmethodID createInstance;
+ jmethodID createTlsState;
+ jmethodID createIncrementalState;
+} gPerfettoDataSourceClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+ jmethodID getAndClearAllPendingTracePackets;
+} gTracingContextClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gCreateTlsStateArgsClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gCreateIncrementalStateArgsClassInfo;
+
+static JavaVM* gVm;
+
+struct TlsState {
+ jobject jobj;
+};
+
+struct IncrementalState {
+ jobject jobj;
+};
+
+static void traceAllPendingPackets(JNIEnv* env, jobject jCtx, PerfettoDsTracerIterator* ctx) {
+ jobjectArray packets =
+ (jobjectArray)env
+ ->CallObjectMethod(jCtx,
+ gTracingContextClassInfo.getAndClearAllPendingTracePackets);
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ LOG_ALWAYS_FATAL("Failed to call java context finalize method");
+ }
+
+ int packets_count = env->GetArrayLength(packets);
+ for (int i = 0; i < packets_count; i++) {
+ jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
+
+ jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
+ int buffer_size = env->GetArrayLength(packet_proto_buffer);
+
+ struct PerfettoDsRootTracePacket trace_packet;
+ PerfettoDsTracerPacketBegin(ctx, &trace_packet);
+ PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
+ buffer_size);
+ PerfettoDsTracerPacketEnd(ctx, &trace_packet);
+ }
+}
+
+PerfettoDataSource::PerfettoDataSource(JNIEnv* env, jobject javaDataSource,
+ std::string dataSourceName)
+ : dataSourceName(std::move(dataSourceName)),
+ mJavaDataSource(env->NewGlobalRef(javaDataSource)) {}
+
+jobject PerfettoDataSource::newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
+ PerfettoDsInstanceIndex inst_id) {
+ jbyteArray configArray = env->NewByteArray(ds_config_size);
+
+ void* temp = env->GetPrimitiveArrayCritical((jarray)configArray, 0);
+ memcpy(temp, ds_config, ds_config_size);
+ env->ReleasePrimitiveArrayCritical(configArray, temp, 0);
+
+ jobject instance =
+ env->CallObjectMethod(mJavaDataSource, gPerfettoDataSourceClassInfo.createInstance,
+ configArray, inst_id);
+
+ if (env->ExceptionCheck()) {
+ LOGE_EX(env);
+ env->ExceptionClear();
+ LOG_ALWAYS_FATAL("Failed to create new Java Perfetto datasource instance");
+ }
+
+ return instance;
+}
+
+jobject PerfettoDataSource::createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gCreateTlsStateArgsClassInfo.clazz,
+ gCreateTlsStateArgsClassInfo.init, mJavaDataSource,
+ inst_id));
+
+ ScopedLocalRef<jobject> tslState(env,
+ env->CallObjectMethod(mJavaDataSource,
+ gPerfettoDataSourceClassInfo
+ .createTlsState,
+ args.get()));
+
+ if (env->ExceptionCheck()) {
+ LOGE_EX(env);
+ env->ExceptionClear();
+ LOG_ALWAYS_FATAL("Failed to create new Java Perfetto incremental state");
+ }
+
+ return env->NewGlobalRef(tslState.get());
+}
+
+jobject PerfettoDataSource::createIncrementalStateGlobalRef(JNIEnv* env,
+ PerfettoDsInstanceIndex inst_id) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gCreateIncrementalStateArgsClassInfo.clazz,
+ gCreateIncrementalStateArgsClassInfo.init,
+ mJavaDataSource, inst_id));
+
+ ScopedLocalRef<jobject> incrementalState(env,
+ env->CallObjectMethod(mJavaDataSource,
+ gPerfettoDataSourceClassInfo
+ .createIncrementalState,
+ args.get()));
+
+ if (env->ExceptionCheck()) {
+ LOGE_EX(env);
+ env->ExceptionClear();
+ LOG_ALWAYS_FATAL("Failed to create Java Perfetto incremental state");
+ }
+
+ return env->NewGlobalRef(incrementalState.get());
+}
+
+void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
+ PERFETTO_DS_TRACE(dataSource, ctx) {
+ TlsState* tls_state =
+ reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
+ IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
+ PerfettoDsGetIncrementalState(&dataSource, &ctx));
+
+ ScopedLocalRef<jobject> jCtx(env,
+ env->NewObject(gTracingContextClassInfo.clazz,
+ gTracingContextClassInfo.init, &ctx,
+ tls_state->jobj, incr_state->jobj));
+
+ jclass objclass = env->GetObjectClass(traceFunction);
+ jmethodID method =
+ env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
+ if (method == 0) {
+ LOG_ALWAYS_FATAL("Failed to get method id");
+ }
+
+ env->ExceptionClear();
+
+ env->CallVoidMethod(traceFunction, method, jCtx.get());
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ LOG_ALWAYS_FATAL("Failed to call java trace method");
+ }
+
+ traceAllPendingPackets(env, jCtx.get(), &ctx);
+ }
+}
+
+void PerfettoDataSource::flushAll() {
+ PERFETTO_DS_TRACE(dataSource, ctx) {
+ PerfettoDsTracerFlush(&ctx, nullptr, nullptr);
+ }
+}
+
+PerfettoDataSource::~PerfettoDataSource() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(mJavaDataSource);
+}
+
+jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
+ const char* nativeString = env->GetStringUTFChars(name, 0);
+ PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
+ env->ReleaseStringUTFChars(name, nativeString);
+
+ dataSource->incStrong((void*)nativeCreate);
+
+ return reinterpret_cast<jlong>(dataSource);
+}
+
+void nativeDestroy(void* ptr) {
+ PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
+ dataSource->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
+}
+
+void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+
+ datasource->trace(env, traceFunctionInterface);
+}
+
+void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
+ auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
+ traceAllPendingPackets(env, jCtx, ctx);
+ PerfettoDsTracerFlush(ctx, nullptr, nullptr);
+}
+
+void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
+ datasource->flushAll();
+}
+
+void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
+ int buffer_exhausted_policy) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
+
+ struct PerfettoDsParams params = PerfettoDsParamsDefault();
+ params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy;
+
+ params.user_arg = reinterpret_cast<void*>(datasource.get());
+
+ params.on_setup_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id,
+ void* ds_config, size_t ds_config_size, void* user_arg,
+ struct PerfettoDsOnSetupArgs*) -> void* {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+
+ ScopedLocalRef<jobject> java_data_source_instance(env,
+ datasource->newInstance(env, ds_config,
+ ds_config_size,
+ inst_id));
+
+ auto* datasource_instance =
+ new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
+
+ return static_cast<void*>(datasource_instance);
+ };
+
+ params.on_create_tls_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
+ struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+
+ jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id);
+
+ auto* tls_state = new TlsState(java_tls_state);
+
+ return static_cast<void*>(tls_state);
+ };
+
+ params.on_delete_tls_cb = [](void* ptr) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
+ env->DeleteGlobalRef(tls_state->jobj);
+ delete tls_state;
+ };
+
+ params.on_create_incr_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
+ struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+ jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
+
+ auto* incr_state = new IncrementalState(java_incr_state);
+ return static_cast<void*>(incr_state);
+ };
+
+ params.on_delete_incr_cb = [](void* ptr) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
+ env->DeleteGlobalRef(incr_state->jobj);
+ delete incr_state;
+ };
+
+ params.on_start_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
+ struct PerfettoDsOnStartArgs*) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+ datasource_instance->onStart(env);
+ };
+
+ params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
+ struct PerfettoDsOnFlushArgs*) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+ datasource_instance->onFlush(env);
+ };
+
+ params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg,
+ void* inst_ctx, struct PerfettoDsOnStopArgs*) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+ datasource_instance->onStop(env);
+ };
+
+ params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg,
+ void* inst_ctx) -> void {
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+ delete datasource_instance;
+ };
+
+ PerfettoDsRegister(&datasource->dataSource, datasource->dataSourceName.c_str(), params);
+}
+
+jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+ PerfettoDsInstanceIndex instance_idx) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
+ PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
+
+ if (datasource_instance == nullptr) {
+ // datasource instance doesn't exist
+ return nullptr;
+ }
+
+ return datasource_instance->GetJavaDataSourceInstance();
+}
+
+void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+ PerfettoDsInstanceIndex instance_idx) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
+}
+
+const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeCreate", "(Landroid/tracing/perfetto/DataSource;Ljava/lang/String;)J",
+ (void*)nativeCreate},
+ {"nativeTrace", "(JLandroid/tracing/perfetto/TraceFunction;)V", (void*)nativeTrace},
+ {"nativeFlushAll", "(J)V", (void*)nativeFlushAll},
+ {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
+ {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource},
+ {"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;",
+ (void*)nativeGetPerfettoInstanceLocked},
+ {"nativeReleasePerfettoInstanceLocked", "(JI)V",
+ (void*)nativeReleasePerfettoInstanceLocked},
+};
+
+const JNINativeMethod gMethodsTracingContext[] = {
+ /* name, signature, funcPtr */
+ {"nativeFlush", "(Landroid/tracing/perfetto/TracingContext;J)V", (void*)nativeFlush},
+};
+
+int register_android_tracing_PerfettoDataSource(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/DataSource", gMethods,
+ NELEM(gMethods));
+
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ res = jniRegisterNativeMethods(env, "android/tracing/perfetto/TracingContext",
+ gMethodsTracingContext, NELEM(gMethodsTracingContext));
+
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ if (env->GetJavaVM(&gVm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env);
+ }
+
+ jclass clazz = env->FindClass("android/tracing/perfetto/DataSource");
+ gPerfettoDataSourceClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gPerfettoDataSourceClassInfo.createInstance =
+ env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createInstance",
+ "([BI)Landroid/tracing/perfetto/DataSourceInstance;");
+ gPerfettoDataSourceClassInfo.createTlsState =
+ env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createTlsState",
+ "(Landroid/tracing/perfetto/CreateTlsStateArgs;)Ljava/lang/Object;");
+ gPerfettoDataSourceClassInfo.createIncrementalState =
+ env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createIncrementalState",
+ "(Landroid/tracing/perfetto/CreateIncrementalStateArgs;)Ljava/lang/"
+ "Object;");
+
+ clazz = env->FindClass("android/tracing/perfetto/TracingContext");
+ gTracingContextClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gTracingContextClassInfo.init = env->GetMethodID(gTracingContextClassInfo.clazz, "<init>",
+ "(JLjava/lang/Object;Ljava/lang/Object;)V");
+ gTracingContextClassInfo.getAndClearAllPendingTracePackets =
+ env->GetMethodID(gTracingContextClassInfo.clazz, "getAndClearAllPendingTracePackets",
+ "()[[B");
+
+ clazz = env->FindClass("android/tracing/perfetto/CreateTlsStateArgs");
+ gCreateTlsStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gCreateTlsStateArgsClassInfo.init =
+ env->GetMethodID(gCreateTlsStateArgsClassInfo.clazz, "<init>",
+ "(Landroid/tracing/perfetto/DataSource;I)V");
+
+ clazz = env->FindClass("android/tracing/perfetto/CreateIncrementalStateArgs");
+ gCreateIncrementalStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gCreateIncrementalStateArgsClassInfo.init =
+ env->GetMethodID(gCreateIncrementalStateArgsClassInfo.clazz, "<init>",
+ "(Landroid/tracing/perfetto/DataSource;I)V");
+
+ return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
new file mode 100644
index 0000000..4ddf1d8
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "android_tracing_PerfettoDataSourceInstance.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+class PerfettoDataSource : public virtual RefBase {
+public:
+ const std::string dataSourceName;
+ struct PerfettoDs dataSource = PERFETTO_DS_INIT();
+
+ PerfettoDataSource(JNIEnv* env, jobject java_data_source, std::string data_source_name);
+ ~PerfettoDataSource();
+
+ jobject newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
+ PerfettoDsInstanceIndex inst_id);
+
+ jobject createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
+ jobject createIncrementalStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
+ void trace(JNIEnv* env, jobject trace_function);
+ void flushAll();
+
+private:
+ jobject mJavaDataSource;
+ std::map<PerfettoDsInstanceIndex, PerfettoDataSourceInstance*> mInstances;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
new file mode 100644
index 0000000..e659bf1
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include "android_tracing_PerfettoDataSourceInstance.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gStartCallbackArgumentsClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gFlushCallbackArgumentsClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gStopCallbackArgumentsClassInfo;
+
+static JavaVM* gVm;
+
+void callJavaMethodWithArgsObject(JNIEnv* env, jobject classRef, jmethodID method, jobject args) {
+ ScopedLocalRef<jobject> localClassRef(env, env->NewLocalRef(classRef));
+
+ if (localClassRef == nullptr) {
+ ALOGE("Weak reference went out of scope");
+ return;
+ }
+
+ env->CallVoidMethod(localClassRef.get(), method, args);
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+}
+
+PerfettoDataSourceInstance::PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance,
+ PerfettoDsInstanceIndex inst_idx)
+ : inst_idx(inst_idx), mJavaDataSourceInstance(env->NewGlobalRef(javaDataSourceInstance)) {}
+
+PerfettoDataSourceInstance::~PerfettoDataSourceInstance() {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+ env->DeleteGlobalRef(mJavaDataSourceInstance);
+}
+
+void PerfettoDataSourceInstance::onStart(JNIEnv* env) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gStartCallbackArgumentsClassInfo.clazz,
+ gStartCallbackArgumentsClassInfo.init));
+ jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+ jmethodID mid = env->GetMethodID(cls, "onStart",
+ "(Landroid/tracing/perfetto/StartCallbackArguments;)V");
+
+ callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+void PerfettoDataSourceInstance::onFlush(JNIEnv* env) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gFlushCallbackArgumentsClassInfo.clazz,
+ gFlushCallbackArgumentsClassInfo.init));
+ jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+ jmethodID mid = env->GetMethodID(cls, "onFlush",
+ "(Landroid/tracing/perfetto/FlushCallbackArguments;)V");
+
+ callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+void PerfettoDataSourceInstance::onStop(JNIEnv* env) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gStopCallbackArgumentsClassInfo.clazz,
+ gStopCallbackArgumentsClassInfo.init));
+ jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+ jmethodID mid =
+ env->GetMethodID(cls, "onStop", "(Landroid/tracing/perfetto/StopCallbackArguments;)V");
+
+ callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env) {
+ if (env->GetJavaVM(&gVm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env);
+ }
+
+ jclass clazz = env->FindClass("android/tracing/perfetto/StartCallbackArguments");
+ gStartCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gStartCallbackArgumentsClassInfo.init =
+ env->GetMethodID(gStartCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+ clazz = env->FindClass("android/tracing/perfetto/FlushCallbackArguments");
+ gFlushCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gFlushCallbackArgumentsClassInfo.init =
+ env->GetMethodID(gFlushCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+ clazz = env->FindClass("android/tracing/perfetto/StopCallbackArguments");
+ gStopCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gStopCallbackArgumentsClassInfo.init =
+ env->GetMethodID(gStopCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+ return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
new file mode 100644
index 0000000..d577655
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+class PerfettoDataSourceInstance {
+public:
+ PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance,
+ PerfettoDsInstanceIndex inst_idx);
+ ~PerfettoDataSourceInstance();
+
+ void onStart(JNIEnv* env);
+ void onFlush(JNIEnv* env);
+ void onStop(JNIEnv* env);
+
+ jobject GetJavaDataSourceInstance() {
+ return mJavaDataSourceInstance;
+ }
+
+ PerfettoDsInstanceIndex getIndex() {
+ return inst_idx;
+ }
+
+private:
+ PerfettoDsInstanceIndex inst_idx;
+ jobject mJavaDataSourceInstance;
+};
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp
new file mode 100644
index 0000000..ce72f58
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoProducer.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "android_tracing_PerfettoDataSource.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+void perfettoProducerInit(JNIEnv* env, jclass clazz, int backends) {
+ struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
+ args.backends = (PerfettoBackendTypes)backends;
+ PerfettoProducerInit(args);
+}
+
+const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativePerfettoProducerInit", "(I)V", (void*)perfettoProducerInit},
+};
+
+int register_android_tracing_PerfettoProducer(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/Producer", gMethods,
+ NELEM(gMethods));
+
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c058174..531756e 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -83,6 +83,10 @@
"com.android.text.flags-aconfig-java",
"flag-junit",
"ravenwood-junit",
+ "perfetto_trace_java_protos",
+ "flickerlib-parsers",
+ "flickerlib-trace_processor_shell",
+ "mockito-target-extended-minus-junit4",
],
libs: [
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
new file mode 100644
index 0000000..b278dba
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -0,0 +1,664 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import static perfetto.protos.PerfettoTrace.TestEvent.PAYLOAD;
+import static perfetto.protos.PerfettoTrace.TestEvent.TestPayload.SINGLE_INT;
+import static perfetto.protos.PerfettoTrace.TracePacket.FOR_TESTING;
+
+import android.tools.common.ScenarioBuilder;
+import android.tools.common.Tag;
+import android.tools.common.io.TraceType;
+import android.tools.device.traces.TraceConfig;
+import android.tools.device.traces.TraceConfigs;
+import android.tools.device.traces.io.ResultReader;
+import android.tools.device.traces.io.ResultWriter;
+import android.tools.device.traces.monitors.PerfettoTraceMonitor;
+import android.tools.device.traces.monitors.TraceMonitor;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import perfetto.protos.PerfettoConfig;
+import perfetto.protos.PerfettoTrace;
+import perfetto.protos.TracePacketOuterClass;
+
+@RunWith(AndroidJUnit4.class)
+public class DataSourceTest {
+ private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+ private final ResultWriter mWriter = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ private final TraceConfigs mTraceConfig = new TraceConfigs(
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false)
+ );
+
+ private static TestDataSource sTestDataSource;
+
+ private static TestDataSource.DataSourceInstanceProvider sInstanceProvider;
+ private static TestDataSource.TlsStateProvider sTlsStateProvider;
+ private static TestDataSource.IncrementalStateProvider sIncrementalStateProvider;
+
+ public DataSourceTest() throws IOException {}
+
+ @BeforeClass
+ public static void beforeAll() {
+ Producer.init(InitArguments.DEFAULTS);
+ setupProviders();
+ sTestDataSource = new TestDataSource(
+ (ds, idx, configStream) -> sInstanceProvider.provide(ds, idx, configStream),
+ args -> sTlsStateProvider.provide(args),
+ args -> sIncrementalStateProvider.provide(args));
+ sTestDataSource.register(DataSourceParams.DEFAULTS);
+ }
+
+ private static void setupProviders() {
+ sInstanceProvider = (ds, idx, configStream) ->
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ sTlsStateProvider = args -> new TestDataSource.TestTlsState();
+ sIncrementalStateProvider = args -> new TestDataSource.TestIncrementalState();
+ }
+
+ @Before
+ public void setup() {
+ setupProviders();
+ }
+
+ @Test
+ public void canTraceData() throws InvalidProtocolBufferException {
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, 10);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt() == 10).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ @Test
+ public void canUseTlsStateForCustomState() {
+ final int expectedStateTestValue = 10;
+ final AtomicInteger actualStateTestValue = new AtomicInteger();
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = expectedStateTestValue;
+ });
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ actualStateTestValue.set(state.testStateValue);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(actualStateTestValue.get()).isEqualTo(expectedStateTestValue);
+ }
+
+ @Test
+ public void eachInstanceHasOwnTlsState() {
+ final int[] expectedStateTestValues = new int[] { 1, 2 };
+ final int[] actualStateTestValues = new int[] { 0, 0 };
+
+ final TraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+ final TraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor1.start();
+ try {
+ traceMonitor2.start();
+
+ AtomicInteger index = new AtomicInteger(0);
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = expectedStateTestValues[index.getAndIncrement()];
+ });
+
+ index.set(0);
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ actualStateTestValues[index.getAndIncrement()] = state.testStateValue;
+ });
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(actualStateTestValues[0]).isEqualTo(expectedStateTestValues[0]);
+ Truth.assertThat(actualStateTestValues[1]).isEqualTo(expectedStateTestValues[1]);
+ }
+
+ @Test
+ public void eachThreadHasOwnTlsState() throws InterruptedException {
+ final int thread1ExpectedStateValue = 1;
+ final int thread2ExpectedStateValue = 2;
+
+ final AtomicInteger thread1ActualStateValue = new AtomicInteger();
+ final AtomicInteger thread2ActualStateValue = new AtomicInteger();
+
+ final CountDownLatch setUpLatch = new CountDownLatch(2);
+ final CountDownLatch setStateLatch = new CountDownLatch(2);
+ final CountDownLatch setOutStateLatch = new CountDownLatch(2);
+
+ final RunnableCreator createTask = (stateValue, stateOut) -> () -> {
+ Producer.init(InitArguments.DEFAULTS);
+
+ setUpLatch.countDown();
+
+ try {
+ setUpLatch.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = stateValue;
+ setStateLatch.countDown();
+ });
+
+ try {
+ setStateLatch.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ sTestDataSource.trace((ctx) -> {
+ stateOut.set(ctx.getCustomTlsState().testStateValue);
+ setOutStateLatch.countDown();
+ });
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ new Thread(
+ createTask.create(thread1ExpectedStateValue, thread1ActualStateValue)).start();
+ new Thread(
+ createTask.create(thread2ExpectedStateValue, thread2ActualStateValue)).start();
+
+ setOutStateLatch.await(3, TimeUnit.SECONDS);
+
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(thread1ActualStateValue.get()).isEqualTo(thread1ExpectedStateValue);
+ Truth.assertThat(thread2ActualStateValue.get()).isEqualTo(thread2ExpectedStateValue);
+ }
+
+ @Test
+ public void incrementalStateIsReset() throws InterruptedException {
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build())
+ .setIncrementalTimeout(10)
+ .build();
+
+ final AtomicInteger testStateValue = new AtomicInteger();
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace(ctx -> ctx.getIncrementalState().testStateValue = 1);
+
+ // Timeout to make sure the incremental state is cleared.
+ Thread.sleep(1000);
+
+ sTestDataSource.trace(ctx ->
+ testStateValue.set(ctx.getIncrementalState().testStateValue));
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(testStateValue.get()).isNotEqualTo(1);
+ }
+
+ @Test
+ public void getInstanceConfigOnCreateInstance() throws IOException {
+ final int expectedDummyIntValue = 10;
+ AtomicReference<ProtoInputStream> configStream = new AtomicReference<>();
+ sInstanceProvider = (ds, idx, config) -> {
+ configStream.set(config);
+ return new TestDataSource.TestDataSourceInstance(ds, idx);
+ };
+
+ final TraceMonitor monitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name)
+ .setForTesting(PerfettoConfig.TestConfig.newBuilder().setDummyFields(
+ PerfettoConfig.TestConfig.DummyFields.newBuilder()
+ .setFieldInt32(expectedDummyIntValue)
+ .build())
+ .build())
+ .build())
+ .build();
+
+ try {
+ monitor.start();
+ } finally {
+ monitor.stop(mWriter);
+ }
+
+ int configDummyIntValue = 0;
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) PerfettoTrace.DataSourceConfig.FOR_TESTING) {
+ final long forTestingToken = configStream.get()
+ .start(PerfettoTrace.DataSourceConfig.FOR_TESTING);
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) PerfettoTrace.TestConfig.DUMMY_FIELDS) {
+ final long dummyFieldsToken = configStream.get()
+ .start(PerfettoTrace.TestConfig.DUMMY_FIELDS);
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) PerfettoTrace.TestConfig.DummyFields.FIELD_INT32) {
+ int val = configStream.get().readInt(
+ PerfettoTrace.TestConfig.DummyFields.FIELD_INT32);
+ if (val != 0) {
+ configDummyIntValue = val;
+ break;
+ }
+ }
+ }
+ configStream.get().end(dummyFieldsToken);
+ break;
+ }
+ }
+ configStream.get().end(forTestingToken);
+ break;
+ }
+ }
+
+ Truth.assertThat(configDummyIntValue).isEqualTo(expectedDummyIntValue);
+ }
+
+ @Test
+ public void multipleTraceInstances() throws IOException, InterruptedException {
+ final int instanceCount = 3;
+
+ final List<TraceMonitor> monitors = new ArrayList<>();
+ final List<ResultWriter> writers = new ArrayList<>();
+
+ for (int i = 0; i < instanceCount; i++) {
+ final ResultWriter writer = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+ writers.add(writer);
+ }
+
+ // Start at 1 because 0 is considered null value so payload will be ignored in that case
+ TestDataSource.TestTlsState.lastIndex = 1;
+
+ final AtomicInteger traceCallCount = new AtomicInteger();
+ final CountDownLatch latch = new CountDownLatch(instanceCount);
+
+ try {
+ // Start instances
+ for (int i = 0; i < instanceCount; i++) {
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+ monitors.add(traceMonitor);
+ traceMonitor.start();
+ }
+
+ // Trace the stateIndex of the tracing instance.
+ sTestDataSource.trace(ctx -> {
+ final int testIntValue = ctx.getCustomTlsState().stateIndex;
+ traceCallCount.incrementAndGet();
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ long forTestingToken = os.start(FOR_TESTING);
+ long payloadToken = os.start(PAYLOAD);
+ os.write(SINGLE_INT, testIntValue);
+ os.end(payloadToken);
+ os.end(forTestingToken);
+
+ latch.countDown();
+ });
+ } finally {
+ // Stop instances
+ for (int i = 0; i < instanceCount; i++) {
+ final TraceMonitor monitor = monitors.get(i);
+ final ResultWriter writer = writers.get(i);
+ monitor.stop(writer);
+ }
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(traceCallCount.get()).isEqualTo(instanceCount);
+
+ for (int i = 0; i < instanceCount; i++) {
+ final int expectedTracedValue = i + 1;
+ final ResultWriter writer = writers.get(i);
+ final ResultReader reader = new ResultReader(writer.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace =
+ perfetto.protos.TraceOuterClass.Trace.parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ Truth.assertWithMessage("One packet has for testing data")
+ .that(tracePackets).hasSize(1);
+
+ final List<TracePacketOuterClass.TracePacket> matchingPackets =
+ tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload()
+ .getSingleInt() == expectedTracedValue).toList();
+ Truth.assertWithMessage(
+ "One packet has testing data with a payload with the expected value")
+ .that(matchingPackets).hasSize(1);
+ }
+ }
+
+ @Test
+ public void onStartCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) -> new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ },
+ (args) -> {},
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ try {
+ traceMonitor.start();
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+ }
+
+ @Test
+ public void onFlushCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ },
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, 10);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ public void onStopCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> {},
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ }
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ public void canUseDataSourceInstanceToCreateTlsState() throws InvalidProtocolBufferException {
+ final Object testObject = new Object();
+
+ sInstanceProvider = (ds, idx, configStream) -> {
+ final TestDataSource.TestDataSourceInstance dsInstance =
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ dsInstance.testObject = testObject;
+ return dsInstance;
+ };
+
+ sTlsStateProvider = args -> {
+ final TestDataSource.TestTlsState tlsState = new TestDataSource.TestTlsState();
+
+ try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+ args.getDataSourceInstanceLocked()) {
+ if (dataSourceInstance != null) {
+ tlsState.testStateValue = dataSourceInstance.testObject.hashCode();
+ }
+ }
+
+ return tlsState;
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, ctx.getCustomTlsState().testStateValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == testObject.hashCode()).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ @Test
+ public void canUseDataSourceInstanceToCreateIncrementalState()
+ throws InvalidProtocolBufferException {
+ final Object testObject = new Object();
+
+ sInstanceProvider = (ds, idx, configStream) -> {
+ final TestDataSource.TestDataSourceInstance dsInstance =
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ dsInstance.testObject = testObject;
+ return dsInstance;
+ };
+
+ sIncrementalStateProvider = args -> {
+ final TestDataSource.TestIncrementalState incrementalState =
+ new TestDataSource.TestIncrementalState();
+
+ try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+ args.getDataSourceInstanceLocked()) {
+ if (dataSourceInstance != null) {
+ incrementalState.testStateValue = dataSourceInstance.testObject.hashCode();
+ }
+ }
+
+ return incrementalState;
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, ctx.getIncrementalState().testStateValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == testObject.hashCode()).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ interface RunnableCreator {
+ Runnable create(int state, AtomicInteger stateOut);
+ }
+}
diff --git a/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
new file mode 100644
index 0000000..d78f78b
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public class TestDataSource extends DataSource<TestDataSource.TestDataSourceInstance,
+ TestDataSource.TestTlsState, TestDataSource.TestIncrementalState> {
+ private final DataSourceInstanceProvider mDataSourceInstanceProvider;
+ private final TlsStateProvider mTlsStateProvider;
+ private final IncrementalStateProvider mIncrementalStateProvider;
+
+ interface DataSourceInstanceProvider {
+ TestDataSourceInstance provide(
+ TestDataSource dataSource, int instanceIndex, ProtoInputStream configStream);
+ }
+
+ interface TlsStateProvider {
+ TestTlsState provide(CreateTlsStateArgs<TestDataSourceInstance> args);
+ }
+
+ interface IncrementalStateProvider {
+ TestIncrementalState provide(CreateIncrementalStateArgs<TestDataSourceInstance> args);
+ }
+
+ public TestDataSource() {
+ this((ds, idx, config) -> new TestDataSourceInstance(ds, idx),
+ args -> new TestTlsState(), args -> new TestIncrementalState());
+ }
+
+ public TestDataSource(
+ DataSourceInstanceProvider dataSourceInstanceProvider,
+ TlsStateProvider tlsStateProvider,
+ IncrementalStateProvider incrementalStateProvider
+ ) {
+ super("android.tracing.perfetto.TestDataSource#" + UUID.randomUUID().toString());
+ this.mDataSourceInstanceProvider = dataSourceInstanceProvider;
+ this.mTlsStateProvider = tlsStateProvider;
+ this.mIncrementalStateProvider = incrementalStateProvider;
+ }
+
+ @Override
+ public TestDataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+ return mDataSourceInstanceProvider.provide(this, instanceIndex, configStream);
+ }
+
+ @Override
+ public TestTlsState createTlsState(CreateTlsStateArgs args) {
+ return mTlsStateProvider.provide(args);
+ }
+
+ @Override
+ public TestIncrementalState createIncrementalState(CreateIncrementalStateArgs args) {
+ return mIncrementalStateProvider.provide(args);
+ }
+
+ public static class TestTlsState {
+ public int testStateValue;
+ public int stateIndex = lastIndex++;
+
+ public static int lastIndex = 0;
+ }
+
+ public static class TestIncrementalState {
+ public int testStateValue;
+ }
+
+ public static class TestDataSourceInstance extends DataSourceInstance {
+ public Object testObject;
+ Consumer<StartCallbackArguments> mStartCallback;
+ Consumer<FlushCallbackArguments> mFlushCallback;
+ Consumer<StopCallbackArguments> mStopCallback;
+
+ public TestDataSourceInstance(DataSource dataSource, int instanceIndex) {
+ this(dataSource, instanceIndex, args -> {}, args -> {}, args -> {});
+ }
+
+ public TestDataSourceInstance(
+ DataSource dataSource,
+ int instanceIndex,
+ Consumer<StartCallbackArguments> startCallback,
+ Consumer<FlushCallbackArguments> flushCallback,
+ Consumer<StopCallbackArguments> stopCallback) {
+ super(dataSource, instanceIndex);
+ this.mStartCallback = startCallback;
+ this.mFlushCallback = flushCallback;
+ this.mStopCallback = stopCallback;
+ }
+
+ @Override
+ public void onStart(StartCallbackArguments args) {
+ this.mStartCallback.accept(args);
+ }
+
+ @Override
+ public void onFlush(FlushCallbackArguments args) {
+ this.mFlushCallback.accept(args);
+ }
+
+ @Override
+ public void onStop(StopCallbackArguments args) {
+ this.mStopCallback.accept(args);
+ }
+ }
+}