Merge "Add window manager tracing"
diff --git a/core/proto/android/server/windowmanagertrace.proto b/core/proto/android/server/windowmanagertrace.proto
new file mode 100644
index 0000000..0c65bb2
--- /dev/null
+++ b/core/proto/android/server/windowmanagertrace.proto
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/content/configuration.proto";
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
+import "frameworks/base/core/proto/android/view/displayinfo.proto";
+import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
+
+package com.android.server.wm.proto;
+
+option java_multiple_files = true;
+
+/* represents a file full of window manager trace entries.
+ Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 (.WINTRACE), such
+ that they can be easily identified. */
+message WindowManagerTraceFileProto {
+
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x544e4957; /* WINT (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
+ }
+
+ optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
+ repeated WindowManagerTraceProto entry = 2;
+}
+
+/* one window manager trace entry. */
+message WindowManagerTraceProto {
+ /* required: elapsed realtime in nanos since boot of when this entry was logged */
+ optional fixed64 elapsed_realtime_nanos = 1;
+
+ /* where the trace originated */
+ optional string where = 2;
+
+ optional WindowManagerServiceProto window_manager_service = 3;
+}
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 401547e..8fb2be8 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -119,7 +119,7 @@
} catch (Exception e) {
Slog.e(TAG_WM, "Exception creating Dim surface", e);
} finally {
- service.closeSurfaceTransaction();
+ service.closeSurfaceTransaction("DimLayer.constructSurface");
}
}
@@ -235,7 +235,7 @@
} catch (RuntimeException e) {
Slog.w(TAG, "Failure setting size", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("DimLayer.setBounds");
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fe34834..2f54e0e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1083,7 +1083,7 @@
mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
} finally {
if (!inTransaction) {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setRotationUnchecked");
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 69f557b..5ea0e1d 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -543,7 +543,7 @@
mDimLayer.setBounds(mTmpRect);
mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setResizeDimLayer");
}
}
mLastDimLayerRect.set(mTmpRect);
@@ -558,7 +558,7 @@
mService.openSurfaceTransaction();
mDimLayer.hide();
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setResizeDimLayer");
}
}
mLastDimLayerAlpha = 0f;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 860ff38..79d46ce 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -185,7 +185,7 @@
surfaceControl.setLayerStack(display.getLayerStack());
surfaceControl.show();
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("performDrag");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, "<<< CLOSE TRANSACTION performDrag");
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 9a955de..861fb44 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -458,7 +458,7 @@
+ mSurfaceControl + ": pos=(" +
(int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("notifyMoveLw");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a710441..bcb6e673 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -457,7 +457,7 @@
try {
forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("removeReplacedWindows");
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
}
}
@@ -599,7 +599,7 @@
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index c25b19c..3350fea 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -296,7 +296,7 @@
setRotationInTransaction(originalRotation);
} finally {
if (!inTransaction) {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation");
}
@@ -567,7 +567,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -607,7 +607,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -629,7 +629,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d83630d..13435d7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -678,7 +678,7 @@
mChildren.get(i).forceWindowsScaleableInTransaction(force);
}
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("forceWindowsScaleable");
}
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index a11b333..12f6b5a 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -637,7 +637,7 @@
} else {
showDimLayer();
}
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("updateDimLayerVisibility");
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index e409a68..1912095 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -233,7 +233,7 @@
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("WindowAnimator");
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index af1fa88..bf5f4bc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -161,7 +161,9 @@
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -361,6 +363,8 @@
private static final int TRANSITION_ANIMATION_SCALE = 1;
private static final int ANIMATION_DURATION_SCALE = 2;
+ final WindowTracing mWindowTracing;
+
final private KeyguardDisableHandler mKeyguardDisableHandler;
boolean mKeyguardGoingAway;
// VR Vr2d Display Id.
@@ -820,15 +824,20 @@
/**
* Closes a surface transaction.
+ * @param where debug string indicating where the transaction originated
*/
- void closeSurfaceTransaction() {
+ void closeSurfaceTransaction(String where) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
synchronized (mWindowMap) {
- if (mRoot.mSurfaceTraceEnabled) {
- mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+ try {
+ traceStateLocked(where);
+ } finally {
+ if (mRoot.mSurfaceTraceEnabled) {
+ mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+ }
+ SurfaceControl.closeTransaction();
}
- SurfaceControl.closeTransaction();
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -929,6 +938,12 @@
}, 0);
}
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver result) {
+ new WindowManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
+ }
+
private WindowManagerService(Context context, InputManagerService inputManager,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
WindowManagerPolicy policy) {
@@ -959,6 +974,8 @@
mPolicy = policy;
mTaskSnapshotController = new TaskSnapshotController(this);
+ mWindowTracing = WindowTracing.createDefaultAndStartLooper(context);
+
LocalServices.addService(WindowManagerPolicy.class, mPolicy);
if(mInputManager != null) {
@@ -1068,7 +1085,7 @@
try {
createWatermarkInTransaction();
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("createWatermarkInTransaction");
}
showEmulatorDisplayOverlayIfNeeded();
@@ -3572,7 +3589,7 @@
mCircularDisplayMask = null;
}
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("showCircularMask");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
}
@@ -3597,7 +3614,7 @@
}
mEmulatorDisplayOverlay.setVisibility(true);
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("showEmulatorDisplayOverlay");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
}
@@ -6320,7 +6337,7 @@
* @param proto Stream to write the WindowContainer object to.
* @param trim If true, reduce the amount of data written.
*/
- private void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
+ void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
mPolicy.writeToProto(proto, POLICY);
mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim);
if (mCurrentFocus != null) {
@@ -6339,6 +6356,17 @@
mAppTransition.writeToProto(proto, APP_TRANSITION);
}
+ void traceStateLocked(String where) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
+ try {
+ mWindowTracing.traceStateLocked(where, this);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception while tracing state", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
new file mode 100644
index 0000000..4b98d9d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for WindowManagerService.
+ *
+ * Use with {@code adb shell cmd window ...}.
+ */
+public class WindowManagerShellCommand extends ShellCommand {
+
+ private final WindowManagerService mService;
+
+ public WindowManagerShellCommand(WindowManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ switch (cmd) {
+ case "tracing":
+ return mService.mWindowTracing.onShellCommand(this, getNextArgRequired());
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Window Manager (window) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(" tracing (start | stop)");
+ pw.println(" start or stop window tracing");
+ pw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5266903..86397ae 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -732,7 +732,7 @@
mSurfaceController.setLayerStackInTransaction(getLayerStack());
mSurfaceController.setLayer(mAnimLayer);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("createSurfaceLocked");
}
mLastHidden = true;
@@ -1711,7 +1711,7 @@
Slog.w(TAG, "Error positioning surface of " + mWin
+ " pos=(" + left + "," + top + ")", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setWallpaperOffset");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setWallpaperOffset");
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index edd650a..a214523 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -259,7 +259,7 @@
mSurfaceControl.setLayer(layer);
}
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setLayer");
}
}
}
@@ -385,7 +385,7 @@
try {
mSurfaceControl.setTransparentRegionHint(region);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setTransparentRegion");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setTransparentRegion");
}
@@ -403,7 +403,7 @@
try {
mSurfaceControl.setOpaque(isOpaque);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setOpaqueLocked");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
}
}
@@ -420,7 +420,7 @@
try {
mSurfaceControl.setSecure(isSecure);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setSecure");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index d57fdd2..cd5e475 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -429,7 +429,7 @@
try {
mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
new file mode 100644
index 0000000..5657f6c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.wm.proto.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.proto.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WHERE;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
+
+import android.content.Context;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * A class that allows window manager to dump its state continuously to a trace file, such that a
+ * time series of window manager state can be analyzed after the fact.
+ */
+class WindowTracing {
+
+ private static final String TAG = "WindowTracing";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final Object mLock = new Object();
+ private final File mTraceFile;
+ private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
+
+ private boolean mEnabled;
+ private volatile boolean mEnabledLockFree;
+
+ WindowTracing(File file) {
+ mTraceFile = file;
+ }
+
+ void startTrace(PrintWriter pw) throws IOException {
+ synchronized (mLock) {
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mWriteQueue.clear();
+ mTraceFile.delete();
+ try (OutputStream os = new FileOutputStream(mTraceFile)) {
+ ProtoOutputStream proto = new ProtoOutputStream(os);
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ proto.flush();
+ }
+ mEnabled = mEnabledLockFree = true;
+ }
+ }
+
+ private void logAndPrintln(PrintWriter pw, String msg) {
+ Log.i(TAG, msg);
+ pw.println(msg);
+ pw.flush();
+ }
+
+ void stopTrace(PrintWriter pw) {
+ synchronized (mLock) {
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ mEnabled = mEnabledLockFree = false;
+ while (!mWriteQueue.isEmpty()) {
+ if (mEnabled) {
+ logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
+ throw new IllegalStateException("tracing enabled while waiting for flush.");
+ }
+ try {
+ mLock.wait();
+ mLock.notify();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ }
+ }
+
+ void appendTraceEntry(ProtoOutputStream proto) {
+ if (!mEnabledLockFree) {
+ return;
+ }
+
+ if (!mWriteQueue.offer(proto)) {
+ Log.e(TAG, "Dropping window trace entry, queue full");
+ }
+ }
+
+ void loop() {
+ for (;;) {
+ loopOnce();
+ }
+ }
+
+ @VisibleForTesting
+ void loopOnce() {
+ ProtoOutputStream proto;
+ try {
+ proto = mWriteQueue.take();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+
+ synchronized (mLock) {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
+ try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
+ os.write(proto.getBytes());
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write file " + mTraceFile, e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ mLock.notify();
+ }
+ }
+
+ boolean isEnabled() {
+ return mEnabledLockFree;
+ }
+
+ static WindowTracing createDefaultAndStartLooper(Context context) {
+ File file = new File("/data/system/window_trace.proto");
+ WindowTracing windowTracing = new WindowTracing(file);
+ new Thread(windowTracing::loop, "window_tracing").start();
+ return windowTracing;
+ }
+
+ int onShellCommand(ShellCommand shell, String cmd) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "start":
+ startTrace(pw);
+ return 0;
+ case "stop":
+ stopTrace(pw);
+ return 0;
+ default:
+ pw.println("Unknown command: " + cmd);
+ return -1;
+ }
+ } catch (IOException e) {
+ logAndPrintln(pw, e.toString());
+ throw new RuntimeException(e);
+ }
+ }
+
+ void traceStateLocked(String where, WindowManagerService service) {
+ if (!isEnabled()) {
+ return;
+ }
+ ProtoOutputStream os = new ProtoOutputStream();
+ long tokenOuter = os.start(ENTRY);
+ os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+ os.write(WHERE, where);
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
+ try {
+ long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+ service.writeToProtoLocked(os, true /* trim */);
+ os.end(tokenInner);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ os.end(tokenOuter);
+ appendTraceEntry(os);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java
new file mode 100644
index 0000000..ad9aea7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 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 org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.wm.proto.WindowManagerTraceProto;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Test class for {@link WindowTracing}.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.wm.WindowTracingTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowTracingTest extends WindowTestsBase {
+
+ private static final byte[] MAGIC_HEADER = new byte[] {
+ 0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45,
+ };
+
+ private Context mTestContext;
+ private WindowTracing mWindowTracing;
+ private WindowManagerService mWmMock;
+ private File mFile;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mWmMock = mock(WindowManagerService.class);
+
+ mTestContext = InstrumentationRegistry.getContext();
+
+ mFile = mTestContext.getFileStreamPath("tracing_test.dat");
+ mFile.delete();
+
+ mWindowTracing = new WindowTracing(mFile);
+ }
+
+ @Test
+ public void isEnabled_returnsFalseByDefault() throws Exception {
+ assertFalse(mWindowTracing.isEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsTrueAfterStart() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+ assertTrue(mWindowTracing.isEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsFalseAfterStop() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+ mWindowTracing.stopTrace(mock(PrintWriter.class));
+ assertFalse(mWindowTracing.isEnabled());
+ }
+
+ @Test
+ public void trace_discared_whenNotTracing() throws Exception {
+ mWindowTracing.traceStateLocked("where", mWmMock);
+ verifyZeroInteractions(mWmMock);
+ }
+
+ @Test
+ public void trace_dumpsWindowManagerState_whenTracing() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+ mWindowTracing.traceStateLocked("where", mWmMock);
+
+ verify(mWmMock).writeToProtoLocked(any(), eq(true));
+ }
+
+ @Test
+ public void traceFile_startsWithMagicHeader() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+ mWindowTracing.stopTrace(mock(PrintWriter.class));
+
+ byte[] header = new byte[MAGIC_HEADER.length];
+ try (InputStream is = new FileInputStream(mFile)) {
+ assertEquals(MAGIC_HEADER.length, is.read(header));
+ assertArrayEquals(MAGIC_HEADER, header);
+ }
+ }
+
+ @Test
+ @Ignore("Figure out why this test is crashing when setting up mWmMock.")
+ public void tracing_endsUpInFile() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+
+ doAnswer((inv) -> {
+ inv.<ProtoOutputStream>getArgument(0).write(
+ WindowManagerTraceProto.WHERE, "TEST_WM_PROTO");
+ return null;
+ }).when(mWmMock).writeToProtoLocked(any(), any());
+ mWindowTracing.traceStateLocked("TEST_WHERE", mWmMock);
+
+ mWindowTracing.stopTrace(mock(PrintWriter.class));
+
+ byte[] file = new byte[1000];
+ int fileLength;
+ try (InputStream is = new FileInputStream(mFile)) {
+ fileLength = is.read(file);
+ assertTrue(containsBytes(file, fileLength,
+ "TEST_WHERE".getBytes(StandardCharsets.UTF_8)));
+ assertTrue(containsBytes(file, fileLength,
+ "TEST_WM_PROTO".getBytes(StandardCharsets.UTF_8)));
+ }
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ mFile.delete();
+ }
+
+ /** Return true if {@code needle} appears anywhere in {@code haystack[0..length]} */
+ boolean containsBytes(byte[] haystack, int haystackLenght, byte[] needle) {
+ Preconditions.checkArgument(haystackLenght > 0);
+ Preconditions.checkArgument(needle.length > 0);
+
+ outer: for (int i = 0; i <= haystackLenght - needle.length; i++) {
+ for (int j = 0; j < needle.length; j++) {
+ if (haystack[i+j] != needle[j]) {
+ continue outer;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Test
+ public void test_containsBytes() {
+ byte[] haystack = "hello_world".getBytes(StandardCharsets.UTF_8);
+ assertTrue(containsBytes(haystack, haystack.length,
+ "hello".getBytes(StandardCharsets.UTF_8)));
+ assertTrue(containsBytes(haystack, haystack.length,
+ "world".getBytes(StandardCharsets.UTF_8)));
+ assertFalse(containsBytes(haystack, 6,
+ "world".getBytes(StandardCharsets.UTF_8)));
+ assertFalse(containsBytes(haystack, haystack.length,
+ "world_".getBytes(StandardCharsets.UTF_8)));
+ assertFalse(containsBytes(haystack, haystack.length,
+ "absent".getBytes(StandardCharsets.UTF_8)));
+ }
+}