From 6ecbbc746c89775906f882aad980537d170739ad Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Mon, 6 Nov 2023 17:49:36 +0000 Subject: [PATCH] Expose Perfetto DataSource to java Bug: 309630341 Test: atest CoreTracingTests Change-Id: I9e9486ba406aa67fbc73922910ea97429ee4683c --- Android.bp | 1 + .../perfetto/CreateIncrementalStateArgs.java | 43 ++ .../tracing/perfetto/CreateTlsStateArgs.java | 43 ++ .../android/tracing/perfetto/DataSource.java | 163 +++++ .../tracing/perfetto/DataSourceInstance.java | 72 ++ .../tracing/perfetto/DataSourceParams.java | 57 ++ .../perfetto/FlushCallbackArguments.java | 23 + .../tracing/perfetto/InitArguments.java | 54 ++ .../android/tracing/perfetto/Producer.java | 34 + .../perfetto/StartCallbackArguments.java | 23 + .../perfetto/StopCallbackArguments.java | 23 + .../tracing/perfetto/TraceFunction.java | 43 ++ .../tracing/perfetto/TracingContext.java | 110 +++ core/jni/Android.bp | 5 + core/jni/AndroidRuntime.cpp | 7 + .../android_tracing_PerfettoDataSource.cpp | 437 ++++++++++++ core/jni/android_tracing_PerfettoDataSource.h | 59 ++ ...oid_tracing_PerfettoDataSourceInstance.cpp | 138 ++++ ...droid_tracing_PerfettoDataSourceInstance.h | 59 ++ core/jni/android_tracing_PerfettoProducer.cpp | 58 ++ core/tests/coretests/Android.bp | 4 + .../tracing/perfetto/DataSourceTest.java | 664 ++++++++++++++++++ .../tracing/perfetto/TestDataSource.java | 122 ++++ 23 files changed, 2242 insertions(+) create mode 100644 core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java create mode 100644 core/java/android/tracing/perfetto/CreateTlsStateArgs.java create mode 100644 core/java/android/tracing/perfetto/DataSource.java create mode 100644 core/java/android/tracing/perfetto/DataSourceInstance.java create mode 100644 core/java/android/tracing/perfetto/DataSourceParams.java create mode 100644 core/java/android/tracing/perfetto/FlushCallbackArguments.java create mode 100644 core/java/android/tracing/perfetto/InitArguments.java create mode 100644 core/java/android/tracing/perfetto/Producer.java create mode 100644 core/java/android/tracing/perfetto/StartCallbackArguments.java create mode 100644 core/java/android/tracing/perfetto/StopCallbackArguments.java create mode 100644 core/java/android/tracing/perfetto/TraceFunction.java create mode 100644 core/java/android/tracing/perfetto/TracingContext.java create mode 100644 core/jni/android_tracing_PerfettoDataSource.cpp create mode 100644 core/jni/android_tracing_PerfettoDataSource.h create mode 100644 core/jni/android_tracing_PerfettoDataSourceInstance.cpp create mode 100644 core/jni/android_tracing_PerfettoDataSourceInstance.h create mode 100644 core/jni/android_tracing_PerfettoProducer.cpp create mode 100644 core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java create mode 100644 core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java diff --git a/Android.bp b/Android.bp index f3b2ebb4fc17..dde94a1282d7 100644 --- a/Android.bp +++ b/Android.bp @@ -149,6 +149,7 @@ filegroup { ":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 000000000000..819cc8c7beef --- /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 The type of datasource instance this state applied to. + */ +public class CreateIncrementalStateArgs { + private final DataSource 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 000000000000..3fad2d1b3e53 --- /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 The type of datasource instance this state applied to. + */ +public class CreateTlsStateArgs { + private final DataSource 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 000000000000..4e08aeef88e6 --- /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 The type for the DataSource instances that will be created from + * this DataSource type. + * @param The type of the custom TLS state, if any is used. + * @param The type of the custom incremental state, if any is used. + * + * @hide + */ +public abstract class DataSource { + 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). + *

+ * 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. + *

+ * 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 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 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 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. + *

+ * 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 000000000000..49945013ae87 --- /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. + *

+ * 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. + *

+ * 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. + *

+ * 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 000000000000..6cd04e3d9a8b --- /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 000000000000..ecf6aee9ef50 --- /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 000000000000..da8c273fd14e --- /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 000000000000..a1b3eb754157 --- /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 000000000000..9739d271a13f --- /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 000000000000..0cd1a188fa0c --- /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 000000000000..62941df70a48 --- /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 The type of DataSource this tracing context is for. + * @param The type of the custom TLS state, if any is used. + * @param The type of the custom incremental state, if any is used. + * + * @hide + */ +public interface TraceFunction { + + /** + * 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 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 000000000000..0bce26e007a1 --- /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 The type of the datasource this tracing context is for. + * @param The type of the custom TLS state, if any is used. + * @param The type of the custom incremental state, if any is used. + * + * @hide + */ +public class TracingContext { + + private final long mContextPtr; + private final TlsStateType mTlsState; + private final IncrementalStateType mIncrementalState; + private final List 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 2a744e343ccd..c8fd246a255b 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -269,6 +269,9 @@ cc_library_shared_for_libandroid_runtime { "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 @@ cc_library_shared_for_libandroid_runtime { "libscrypt_static", "libstatssocket_lazy", "libskia", + "libperfetto_client_experimental", ], shared_libs: [ @@ -355,6 +359,7 @@ cc_library_shared_for_libandroid_runtime { "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 17aad43edb6b..7a16318f3276 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -220,6 +220,9 @@ extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 000000000000..d71069866d89 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 args(env, + env->NewObject(gCreateTlsStateArgsClassInfo.clazz, + gCreateTlsStateArgsClassInfo.init, mJavaDataSource, + inst_id)); + + ScopedLocalRef 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 args(env, + env->NewObject(gCreateIncrementalStateArgsClassInfo.clazz, + gCreateIncrementalStateArgsClassInfo.init, + mJavaDataSource, inst_id)); + + ScopedLocalRef 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(PerfettoDsGetCustomTls(&dataSource, &ctx)); + IncrementalState* incr_state = reinterpret_cast( + PerfettoDsGetIncrementalState(&dataSource, &ctx)); + + ScopedLocalRef 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(dataSource); +} + +void nativeDestroy(void* ptr) { + PerfettoDataSource* dataSource = reinterpret_cast(ptr); + dataSource->decStrong((void*)nativeCreate); +} + +static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) { + return static_cast(reinterpret_cast(&nativeDestroy)); +} + +void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) { + sp datasource = reinterpret_cast(dataSourcePtr); + + datasource->trace(env, traceFunctionInterface); +} + +void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) { + auto* ctx = reinterpret_cast(ctxPtr); + traceAllPendingPackets(env, jCtx, ctx); + PerfettoDsTracerFlush(ctx, nullptr, nullptr); +} + +void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) { + sp datasource = reinterpret_cast(ptr); + datasource->flushAll(); +} + +void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr, + int buffer_exhausted_policy) { + sp datasource = reinterpret_cast(datasource_ptr); + + struct PerfettoDsParams params = PerfettoDsParamsDefault(); + params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy; + + params.user_arg = reinterpret_cast(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(user_arg); + + ScopedLocalRef 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(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(user_arg); + + jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id); + + auto* tls_state = new TlsState(java_tls_state); + + return static_cast(tls_state); + }; + + params.on_delete_tls_cb = [](void* ptr) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + TlsState* tls_state = reinterpret_cast(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(user_arg); + jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id); + + auto* incr_state = new IncrementalState(java_incr_state); + return static_cast(incr_state); + }; + + params.on_delete_incr_cb = [](void* ptr) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + IncrementalState* incr_state = reinterpret_cast(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(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(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(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(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 datasource = reinterpret_cast(dataSourcePtr); + auto* datasource_instance = static_cast( + 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 datasource = reinterpret_cast(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, "", + "(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, "", + "(Landroid/tracing/perfetto/DataSource;I)V"); + + clazz = env->FindClass("android/tracing/perfetto/CreateIncrementalStateArgs"); + gCreateIncrementalStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gCreateIncrementalStateArgsClassInfo.init = + env->GetMethodID(gCreateIncrementalStateArgsClassInfo.clazz, "", + "(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 000000000000..4ddf1d8d4512 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 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 000000000000..e659bf1c55e9 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 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 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 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 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, "", "()V"); + + clazz = env->FindClass("android/tracing/perfetto/FlushCallbackArguments"); + gFlushCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gFlushCallbackArgumentsClassInfo.init = + env->GetMethodID(gFlushCallbackArgumentsClassInfo.clazz, "", "()V"); + + clazz = env->FindClass("android/tracing/perfetto/StopCallbackArguments"); + gStopCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gStopCallbackArgumentsClassInfo.init = + env->GetMethodID(gStopCallbackArgumentsClassInfo.clazz, "", "()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 000000000000..d57765565d8a --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 000000000000..ce72f5893c19 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 c0581746e6f6..531756ef2302 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -83,6 +83,10 @@ android_test { "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 000000000000..b278dbafd5e1 --- /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 tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List 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 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 monitors = new ArrayList<>(); + final List 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 tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + Truth.assertWithMessage("One packet has for testing data") + .that(tracePackets).hasSize(1); + + final List 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 tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List 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 tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List 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 000000000000..d78f78b1cb0e --- /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 { + 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 args); + } + + interface IncrementalStateProvider { + TestIncrementalState provide(CreateIncrementalStateArgs 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 mStartCallback; + Consumer mFlushCallback; + Consumer mStopCallback; + + public TestDataSourceInstance(DataSource dataSource, int instanceIndex) { + this(dataSource, instanceIndex, args -> {}, args -> {}, args -> {}); + } + + public TestDataSourceInstance( + DataSource dataSource, + int instanceIndex, + Consumer startCallback, + Consumer flushCallback, + Consumer 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); + } + } +}