diff options
| author | 2024-06-21 13:06:00 +0000 | |
|---|---|---|
| committer | 2024-07-12 11:04:02 +0000 | |
| commit | fcfe6dfec755ca1481046bcd73dc0c64827cb7de (patch) | |
| tree | add916b049cd8d116eb6c83b98891c7eb3319642 | |
| parent | 4e2b40239a5f213c3d266dfc0c4a93c51f55419a (diff) | |
wm tracing: migrate to perfetto
Bug: 323165543
Flag: android.tracing.perfetto_wm_tracing
Test: presubmit
Change-Id: Iec0e94d0b9bd029c9e3bfdca5d62d87ea45098ac
3 files changed, 371 insertions, 1 deletions
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 21f7eca5627a..04d5c03e64a6 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -56,7 +56,10 @@ abstract class WindowTracing { static WindowTracing createDefaultAndStartLooper(WindowManagerService service, Choreographer choreographer) { - return new WindowTracingLegacy(service, choreographer); + if (!android.tracing.Flags.perfettoWmTracing()) { + return new WindowTracingLegacy(service, choreographer); + } + return new WindowTracingPerfetto(service, choreographer); } protected WindowTracing(WindowManagerService service, Choreographer choreographer, diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java new file mode 100644 index 000000000000..3d2c0d3f79b9 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java @@ -0,0 +1,204 @@ +/* + * 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 com.android.server.wm; + +import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT; + +import android.annotation.NonNull; +import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig; +import android.internal.perfetto.protos.WindowmanagerConfig.WindowManagerConfig; +import android.tracing.perfetto.CreateTlsStateArgs; +import android.tracing.perfetto.DataSource; +import android.tracing.perfetto.DataSourceInstance; +import android.tracing.perfetto.DataSourceParams; +import android.tracing.perfetto.InitArguments; +import android.tracing.perfetto.Producer; +import android.tracing.perfetto.StartCallbackArguments; +import android.tracing.perfetto.StopCallbackArguments; +import android.util.Log; +import android.util.proto.ProtoInputStream; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance, + WindowTracingDataSource.TlsState, Void> { + public static final String DATA_SOURCE_NAME = "android.windowmanager"; + + public static class TlsState { + public final Config mConfig; + public final AtomicBoolean mIsStarting = new AtomicBoolean(true); + + private TlsState(Config config) { + mConfig = config; + } + } + + public static class Config { + public final @WindowTraceLogLevel int mLogLevel; + public final boolean mLogOnFrame; + + private Config(@WindowTraceLogLevel int logLevel, boolean logOnFrame) { + mLogLevel = logLevel; + mLogOnFrame = logOnFrame; + } + } + + public abstract static class Instance extends DataSourceInstance { + public final Config mConfig; + + public Instance(DataSource dataSource, int instanceIndex, Config config) { + super(dataSource, instanceIndex); + mConfig = config; + } + } + + private static final Config CONFIG_DEFAULT = new Config(WindowTraceLogLevel.TRIM, true); + private static final int CONFIG_VALUE_UNSPECIFIED = 0; + private static final String TAG = "WindowTracingDataSource"; + + @NonNull + private final Consumer<Config> mOnStartCallback; + @NonNull + private final Consumer<Config> mOnStopCallback; + + public WindowTracingDataSource(@NonNull Consumer<Config> onStart, + @NonNull Consumer<Config> onStop) { + super(DATA_SOURCE_NAME); + mOnStartCallback = onStart; + mOnStopCallback = onStop; + + Producer.init(InitArguments.DEFAULTS); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .build(); + register(params); + } + + @Override + public Instance createInstance(ProtoInputStream configStream, int instanceIndex) { + final Config config = parseDataSourceConfig(configStream); + + return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) { + @Override + protected void onStart(StartCallbackArguments args) { + mOnStartCallback.accept(mConfig); + } + + @Override + protected void onStop(StopCallbackArguments args) { + mOnStopCallback.accept(mConfig); + } + }; + } + + @Override + public TlsState createTlsState( + CreateTlsStateArgs<Instance> args) { + try (Instance dsInstance = args.getDataSourceInstanceLocked()) { + if (dsInstance == null) { + // Datasource instance has been removed + return new TlsState(CONFIG_DEFAULT); + } + return new TlsState(dsInstance.mConfig); + } + } + + private Config parseDataSourceConfig(ProtoInputStream stream) { + try { + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (stream.getFieldNumber() != (int) DataSourceConfig.WINDOWMANAGER_CONFIG) { + continue; + } + return parseWindowManagerConfig(stream); + } + Log.w(TAG, "Received start request without config parameters. Will use defaults."); + } catch (IOException e) { + throw new RuntimeException("Failed to parse DataSourceConfig", e); + } + return null; + } + + private Config parseWindowManagerConfig(ProtoInputStream stream) { + int parsedLogLevel = CONFIG_VALUE_UNSPECIFIED; + int parsedLogFrequency = CONFIG_VALUE_UNSPECIFIED; + + try { + final long token = stream.start(DataSourceConfig.WINDOWMANAGER_CONFIG); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) WindowManagerConfig.LOG_LEVEL: + parsedLogLevel = stream.readInt(WindowManagerConfig.LOG_LEVEL); + break; + case (int) WindowManagerConfig.LOG_FREQUENCY: + parsedLogFrequency = stream.readInt(WindowManagerConfig.LOG_FREQUENCY); + break; + default: + Log.w(TAG, "Unrecognized WindowManagerConfig field number: " + + stream.getFieldNumber()); + } + } + stream.end(token); + } catch (IOException e) { + throw new RuntimeException("Failed to parse WindowManagerConfig", e); + } + + @WindowTraceLogLevel int logLevel; + switch(parsedLogLevel) { + case CONFIG_VALUE_UNSPECIFIED: + Log.w(TAG, "Unspecified log level. Defaulting to TRIM"); + logLevel = WindowTraceLogLevel.TRIM; + break; + case WindowManagerConfig.LOG_LEVEL_VERBOSE: + logLevel = WindowTraceLogLevel.ALL; + break; + case WindowManagerConfig.LOG_LEVEL_DEBUG: + logLevel = WindowTraceLogLevel.TRIM; + break; + case WindowManagerConfig.LOG_LEVEL_CRITICAL: + logLevel = WindowTraceLogLevel.CRITICAL; + break; + default: + Log.w(TAG, "Unrecognized log level. Defaulting to TRIM"); + logLevel = WindowTraceLogLevel.TRIM; + break; + } + + boolean logOnFrame; + switch(parsedLogFrequency) { + case CONFIG_VALUE_UNSPECIFIED: + Log.w(TAG, "Unspecified log frequency. Defaulting to 'log on frame'"); + logOnFrame = true; + break; + case WindowManagerConfig.LOG_FREQUENCY_FRAME: + logOnFrame = true; + break; + case WindowManagerConfig.LOG_FREQUENCY_TRANSACTION: + logOnFrame = false; + break; + default: + Log.w(TAG, "Unrecognized log frequency. Defaulting to 'log on frame'"); + logOnFrame = true; + break; + } + + return new Config(logLevel, logOnFrame); + } +} diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java new file mode 100644 index 000000000000..653b6dac1537 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java @@ -0,0 +1,163 @@ +/* + * 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 com.android.server.wm; + +import android.annotation.Nullable; +import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket; +import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl; +import android.os.ShellCommand; +import android.os.SystemClock; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.Choreographer; + +import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicInteger; + +class WindowTracingPerfetto extends WindowTracing { + private static final String TAG = "WindowTracing"; + + private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger(); + private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger(); + private final WindowTracingDataSource mDataSource = new WindowTracingDataSource( + this::onStart, this::onStop); + + WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) { + super(service, choreographer, service.mGlobalLock); + } + + @Override + void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { + logAndPrintln(pw, "Log level must be configured through perfetto"); + } + + @Override + void setLogFrequency(boolean onFrame, PrintWriter pw) { + logAndPrintln(pw, "Log frequency must be configured through perfetto"); + } + + @Override + void setBufferCapacity(int capacity, PrintWriter pw) { + logAndPrintln(pw, "Buffer capacity must be configured through perfetto"); + } + + @Override + boolean isEnabled() { + return (mCountSessionsOnFrame.get() + mCountSessionsOnTransaction.get()) > 0; + } + + @Override + int onShellCommand(ShellCommand shell) { + PrintWriter pw = shell.getOutPrintWriter(); + pw.println("Shell commands are ignored." + + " Any type of action should be performed through perfetto."); + return -1; + } + + @Override + String getStatus() { + return "Status: " + + ((isEnabled()) ? "Enabled" : "Disabled") + + "\n" + + "Sessions logging 'on frame': " + mCountSessionsOnFrame.get() + + "\n" + + "Sessions logging 'on transaction': " + mCountSessionsOnTransaction.get() + + "\n"; + } + + @Override + protected void startTraceInternal(@Nullable PrintWriter pw) { + logAndPrintln(pw, "Tracing must be started through perfetto"); + } + + @Override + protected void stopTraceInternal(@Nullable PrintWriter pw) { + logAndPrintln(pw, "Tracing must be stopped through perfetto"); + } + + @Override + protected void saveForBugreportInternal(@Nullable PrintWriter pw) { + logAndPrintln(pw, "Tracing snapshot for bugreport must be handled through perfetto"); + } + + @Override + protected void log(String where) { + try { + boolean isStartLogEvent = where == WHERE_START_TRACING; + boolean isOnFrameLogEvent = where == WHERE_ON_FRAME; + + mDataSource.trace((context) -> { + WindowTracingDataSource.Config dataSourceConfig = + context.getCustomTlsState().mConfig; + + if (isStartLogEvent) { + boolean isDataSourceStarting = context.getCustomTlsState() + .mIsStarting.compareAndSet(true, false); + if (!isDataSourceStarting) { + return; + } + } else if (isOnFrameLogEvent != dataSourceConfig.mLogOnFrame) { + return; + } + + ProtoOutputStream os = context.newTracePacket(); + long timestamp = SystemClock.elapsedRealtimeNanos(); + os.write(TracePacket.TIMESTAMP, timestamp); + final long tokenWinscopeExtensions = + os.start(TracePacket.WINSCOPE_EXTENSIONS); + final long tokenExtensionsField = + os.start(WinscopeExtensionsImpl.WINDOWMANAGER); + dumpToProto(os, dataSourceConfig.mLogLevel, where, timestamp); + os.end(tokenExtensionsField); + os.end(tokenWinscopeExtensions); + }); + } catch (Exception e) { + Log.wtf(TAG, "Exception while tracing state", e); + } + } + + @Override + protected boolean shouldLogOnFrame() { + return mCountSessionsOnFrame.get() > 0; + } + + @Override + protected boolean shouldLogOnTransaction() { + return mCountSessionsOnTransaction.get() > 0; + } + + private void onStart(WindowTracingDataSource.Config config) { + if (config.mLogOnFrame) { + mCountSessionsOnFrame.incrementAndGet(); + } else { + mCountSessionsOnTransaction.incrementAndGet(); + } + + Log.i(TAG, "Started with logLevel: " + config.mLogLevel + + " logOnFrame: " + config.mLogOnFrame); + log(WHERE_START_TRACING); + } + + private void onStop(WindowTracingDataSource.Config config) { + if (config.mLogOnFrame) { + mCountSessionsOnFrame.decrementAndGet(); + } else { + mCountSessionsOnTransaction.decrementAndGet(); + } + Log.i(TAG, "Stopped"); + } +} |