Loading services/core/java/com/android/server/wm/WindowTraceBuffer.java 0 → 100644 +181 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.WindowManagerTraceFileProto.MAGIC_NUMBER; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; import android.os.Trace; import android.util.proto.ProtoOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** * Buffer used for window tracing. */ abstract class WindowTraceBuffer { private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; final Object mBufferSizeLock = new Object(); final BlockingQueue<byte[]> mBuffer; int mBufferSize; private final int mBufferCapacity; private final File mTraceFile; WindowTraceBuffer(int size, File traceFile) throws IOException { mBufferCapacity = size; mTraceFile = traceFile; mBuffer = new LinkedBlockingQueue<>(); initTraceFile(); } int getAvailableSpace() { return mBufferCapacity - mBufferSize; } /** * Inserts the specified element into this buffer. * * This method is synchronized with {@code #take()} and {@code #clear()} * for consistency. * * @param proto the element to add * @return {@code true} if the inserted item was inserted into the buffer * @throws IllegalStateException if the element cannot be added because it is larger * than the buffer size. */ boolean add(ProtoOutputStream proto) throws InterruptedException { byte[] protoBytes = proto.getBytes(); int protoLength = protoBytes.length; if (protoLength > mBufferCapacity) { throw new IllegalStateException("Trace object too large for the buffer. Buffer size:" + mBufferCapacity + " Object size: " + protoLength); } synchronized (mBufferSizeLock) { boolean canAdd = canAdd(protoBytes); if (canAdd) { mBuffer.offer(protoBytes); mBufferSize += protoLength; } return canAdd; } } void writeNextBufferElementToFile() throws IOException { byte[] proto; try { proto = take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } try { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile"); try (OutputStream os = new FileOutputStream(mTraceFile, true)) { os.write(proto); } } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } } /** * Retrieves and removes the head of this queue, waiting if necessary * until an element becomes available. * * This method is synchronized with {@code #add(ProtoOutputStream)} and {@code #clear()} * for consistency. * * @return the head of this buffer, or {@code null} if this buffer is empty */ private byte[] take() throws InterruptedException { byte[] item = mBuffer.take(); synchronized (mBufferSizeLock) { mBufferSize -= item.length; return item; } } private void initTraceFile() throws IOException { mTraceFile.delete(); try (OutputStream os = new FileOutputStream(mTraceFile)) { mTraceFile.setReadable(true, false); ProtoOutputStream proto = new ProtoOutputStream(os); proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); proto.flush(); } } /** * Checks if the element can be added to the buffer. The element is already certain to be * smaller than the overall buffer size. * * @param protoBytes byte array representation of the Proto object to add * @return <tt>true<</tt> if the element can be added to the buffer or not */ abstract boolean canAdd(byte[] protoBytes) throws InterruptedException; /** * Flush all buffer content to the disk. * * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile} */ abstract void writeToDisk() throws IOException, InterruptedException; /** * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceQueueBuffer} */ static class Builder { private File mTraceFile; private int mBufferCapacity; Builder setTraceFile(File traceFile) { mTraceFile = traceFile; return this; } Builder setBufferCapacity(int size) { mBufferCapacity = size; return this; } File getFile() { return mTraceFile; } WindowTraceBuffer build() throws IOException { if (mBufferCapacity <= 0) { throw new IllegalStateException("Buffer capacity must be greater than 0."); } if (mTraceFile == null) { throw new IllegalArgumentException("A valid trace file must be specified."); } return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile); } } } services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.os.Build.IS_USER; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; /** * A buffer structure backed by a {@link java.util.concurrent.BlockingQueue} to store the first * {@code #size size} bytes of window trace elements. * Once the buffer is full it will no longer accepts new elements. */ class WindowTraceQueueBuffer extends WindowTraceBuffer { private Thread mWriterThread; private boolean mCancel; @VisibleForTesting WindowTraceQueueBuffer(int size, File traceFile, boolean startWriterThread) throws IOException { super(size, traceFile); if (startWriterThread) { initializeWriterThread(); } } WindowTraceQueueBuffer(int size, File traceFile) throws IOException { this(size, traceFile, !IS_USER); } private void initializeWriterThread() { mCancel = false; mWriterThread = new Thread(() -> { try { loop(); } catch (IOException e) { throw new IllegalStateException("Failed to execute trace write loop thread", e); } }, "window_tracing"); mWriterThread.start(); } private void loop() throws IOException { while (!mCancel) { writeNextBufferElementToFile(); } } private void restartWriterThread() throws InterruptedException { if (mWriterThread != null) { mCancel = true; mWriterThread.interrupt(); mWriterThread.join(); initializeWriterThread(); } } @Override boolean canAdd(byte[] protoBytes) { long availableSpace = getAvailableSpace(); return availableSpace >= protoBytes.length; } @Override void writeToDisk() throws InterruptedException { while (!mBuffer.isEmpty()) { mBufferSizeLock.wait(); mBufferSizeLock.notify(); } restartWriterThread(); } } services/core/java/com/android/server/wm/WindowTracing.java +36 −66 Original line number Diff line number Diff line Loading @@ -17,31 +17,23 @@ package com.android.server.wm; import static android.os.Build.IS_USER; import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS; import static com.android.server.wm.WindowManagerTraceProto.WHERE; import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE; import android.annotation.Nullable; import android.content.Context; import android.os.ShellCommand; import android.os.SystemClock; import android.os.Trace; import android.annotation.Nullable; 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 Loading @@ -49,18 +41,25 @@ import java.util.concurrent.BlockingQueue; */ class WindowTracing { /** * Maximum buffer size, currently defined as 512 KB * Size was experimentally defined to fit between 100 to 150 elements. */ private static final int WINDOW_TRACE_BUFFER_SIZE = 512 * 1024; 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 final WindowTraceBuffer.Builder mBufferBuilder; private WindowTraceBuffer mTraceBuffer; private boolean mEnabled; private volatile boolean mEnabledLockFree; WindowTracing(File file) { mTraceFile = file; mBufferBuilder = new WindowTraceBuffer.Builder() .setTraceFile(file) .setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE); } void startTrace(@Nullable PrintWriter pw) throws IOException { Loading @@ -69,15 +68,15 @@ class WindowTracing { return; } synchronized (mLock) { logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); mWriteQueue.clear(); mTraceFile.delete(); try (OutputStream os = new FileOutputStream(mTraceFile)) { mTraceFile.setReadable(true, false); ProtoOutputStream proto = new ProtoOutputStream(os); proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); proto.flush(); logAndPrintln(pw, "Start tracing to " + mBufferBuilder.getFile() + "."); if (mTraceBuffer != null) { try { mTraceBuffer.writeToDisk(); } catch (InterruptedException e) { logAndPrintln(pw, "Error: Unable to flush the previous buffer."); } } mTraceBuffer = mBufferBuilder.build(); mEnabled = mEnabledLockFree = true; } } Loading @@ -96,62 +95,37 @@ class WindowTracing { return; } synchronized (mLock) { logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); logAndPrintln(pw, "Stop tracing to " + mBufferBuilder.getFile() + ". Waiting for traces to flush."); mEnabled = mEnabledLockFree = false; while (!mWriteQueue.isEmpty()) { synchronized (mLock) { 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(); mTraceBuffer.writeToDisk(); } catch (IOException e) { Log.e(TAG, "Unable to write buffer to file", e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.e(TAG, "Unable to interrupt window tracing file write thread", e); } } logAndPrintln(pw, "Trace written to " + mTraceFile + "."); logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + "."); } } void appendTraceEntry(ProtoOutputStream proto) { private 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(); mTraceBuffer.add(proto); } catch (InterruptedException e) { Log.e(TAG, "Unable to add element to trace", 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(); } } Loading @@ -161,11 +135,7 @@ class WindowTracing { static WindowTracing createDefaultAndStartLooper(Context context) { File file = new File("/data/misc/wmtrace/wm_trace.pb"); WindowTracing windowTracing = new WindowTracing(file); if (!IS_USER){ new Thread(windowTracing::loop, "window_tracing").start(); } return windowTracing; return new WindowTracing(file); } int onShellCommand(ShellCommand shell, String cmd) { Loading services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java 0 → 100644 +115 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.platform.test.annotations.Presubmit; import android.util.proto.ProtoOutputStream; import androidx.test.filters.SmallTest; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.util.Arrays; /** * Test class for {@link WindowTraceBuffer} and {@link WindowTraceQueueBuffer}. * * Build/Install/Run: * atest WmTests:WindowTraceBufferTest */ @SmallTest @Presubmit public class WindowTraceBufferTest { private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; private File mFile; @Before public void setUp() throws Exception { final Context testContext = getInstrumentation().getContext(); mFile = testContext.getFileStreamPath("tracing_test.dat"); mFile.delete(); } @After public void tearDown() throws Exception { mFile.delete(); } @Test public void testTraceQueueBuffer_addItem() throws Exception { ProtoOutputStream toWrite1 = getDummy(1); ProtoOutputStream toWrite2 = getDummy(2); ProtoOutputStream toWrite3 = getDummy(3); byte[] toWrite1Bytes = toWrite1.getBytes(); byte[] toWrite2Bytes = toWrite2.getBytes(); byte[] toWrite3Bytes = toWrite3.getBytes(); final int objectSize = toWrite1.getBytes().length; final int bufferCapacity = objectSize * 2; final WindowTraceBuffer buffer = buildQueueBuffer(bufferCapacity); buffer.add(toWrite1); assertTrue("First element should be in the list", buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes))); buffer.add(toWrite2); assertTrue("First element should be in the list", buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes))); assertTrue("Second element should be in the list", buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite2Bytes))); buffer.add(toWrite3); assertTrue("Third element should not be in the list", buffer.mBuffer.stream().noneMatch(p -> Arrays.equals(p, toWrite3Bytes))); assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2); assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity), buffer.mBufferSize, bufferCapacity); assertEquals("Buffer is full, available space should be 0", buffer.getAvailableSpace(), 0); } private ProtoOutputStream getDummy(int value) { ProtoOutputStream toWrite = new ProtoOutputStream(); toWrite.write(MAGIC_NUMBER, value); toWrite.flush(); return toWrite; } private WindowTraceBuffer buildQueueBuffer(int size) throws IOException { return new WindowTraceQueueBuffer(size, mFile, false); } } Loading
services/core/java/com/android/server/wm/WindowTraceBuffer.java 0 → 100644 +181 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.WindowManagerTraceFileProto.MAGIC_NUMBER; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; import android.os.Trace; import android.util.proto.ProtoOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** * Buffer used for window tracing. */ abstract class WindowTraceBuffer { private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; final Object mBufferSizeLock = new Object(); final BlockingQueue<byte[]> mBuffer; int mBufferSize; private final int mBufferCapacity; private final File mTraceFile; WindowTraceBuffer(int size, File traceFile) throws IOException { mBufferCapacity = size; mTraceFile = traceFile; mBuffer = new LinkedBlockingQueue<>(); initTraceFile(); } int getAvailableSpace() { return mBufferCapacity - mBufferSize; } /** * Inserts the specified element into this buffer. * * This method is synchronized with {@code #take()} and {@code #clear()} * for consistency. * * @param proto the element to add * @return {@code true} if the inserted item was inserted into the buffer * @throws IllegalStateException if the element cannot be added because it is larger * than the buffer size. */ boolean add(ProtoOutputStream proto) throws InterruptedException { byte[] protoBytes = proto.getBytes(); int protoLength = protoBytes.length; if (protoLength > mBufferCapacity) { throw new IllegalStateException("Trace object too large for the buffer. Buffer size:" + mBufferCapacity + " Object size: " + protoLength); } synchronized (mBufferSizeLock) { boolean canAdd = canAdd(protoBytes); if (canAdd) { mBuffer.offer(protoBytes); mBufferSize += protoLength; } return canAdd; } } void writeNextBufferElementToFile() throws IOException { byte[] proto; try { proto = take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } try { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile"); try (OutputStream os = new FileOutputStream(mTraceFile, true)) { os.write(proto); } } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } } /** * Retrieves and removes the head of this queue, waiting if necessary * until an element becomes available. * * This method is synchronized with {@code #add(ProtoOutputStream)} and {@code #clear()} * for consistency. * * @return the head of this buffer, or {@code null} if this buffer is empty */ private byte[] take() throws InterruptedException { byte[] item = mBuffer.take(); synchronized (mBufferSizeLock) { mBufferSize -= item.length; return item; } } private void initTraceFile() throws IOException { mTraceFile.delete(); try (OutputStream os = new FileOutputStream(mTraceFile)) { mTraceFile.setReadable(true, false); ProtoOutputStream proto = new ProtoOutputStream(os); proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); proto.flush(); } } /** * Checks if the element can be added to the buffer. The element is already certain to be * smaller than the overall buffer size. * * @param protoBytes byte array representation of the Proto object to add * @return <tt>true<</tt> if the element can be added to the buffer or not */ abstract boolean canAdd(byte[] protoBytes) throws InterruptedException; /** * Flush all buffer content to the disk. * * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile} */ abstract void writeToDisk() throws IOException, InterruptedException; /** * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceQueueBuffer} */ static class Builder { private File mTraceFile; private int mBufferCapacity; Builder setTraceFile(File traceFile) { mTraceFile = traceFile; return this; } Builder setBufferCapacity(int size) { mBufferCapacity = size; return this; } File getFile() { return mTraceFile; } WindowTraceBuffer build() throws IOException { if (mBufferCapacity <= 0) { throw new IllegalStateException("Buffer capacity must be greater than 0."); } if (mTraceFile == null) { throw new IllegalArgumentException("A valid trace file must be specified."); } return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile); } } }
services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.os.Build.IS_USER; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; /** * A buffer structure backed by a {@link java.util.concurrent.BlockingQueue} to store the first * {@code #size size} bytes of window trace elements. * Once the buffer is full it will no longer accepts new elements. */ class WindowTraceQueueBuffer extends WindowTraceBuffer { private Thread mWriterThread; private boolean mCancel; @VisibleForTesting WindowTraceQueueBuffer(int size, File traceFile, boolean startWriterThread) throws IOException { super(size, traceFile); if (startWriterThread) { initializeWriterThread(); } } WindowTraceQueueBuffer(int size, File traceFile) throws IOException { this(size, traceFile, !IS_USER); } private void initializeWriterThread() { mCancel = false; mWriterThread = new Thread(() -> { try { loop(); } catch (IOException e) { throw new IllegalStateException("Failed to execute trace write loop thread", e); } }, "window_tracing"); mWriterThread.start(); } private void loop() throws IOException { while (!mCancel) { writeNextBufferElementToFile(); } } private void restartWriterThread() throws InterruptedException { if (mWriterThread != null) { mCancel = true; mWriterThread.interrupt(); mWriterThread.join(); initializeWriterThread(); } } @Override boolean canAdd(byte[] protoBytes) { long availableSpace = getAvailableSpace(); return availableSpace >= protoBytes.length; } @Override void writeToDisk() throws InterruptedException { while (!mBuffer.isEmpty()) { mBufferSizeLock.wait(); mBufferSizeLock.notify(); } restartWriterThread(); } }
services/core/java/com/android/server/wm/WindowTracing.java +36 −66 Original line number Diff line number Diff line Loading @@ -17,31 +17,23 @@ package com.android.server.wm; import static android.os.Build.IS_USER; import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS; import static com.android.server.wm.WindowManagerTraceProto.WHERE; import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE; import android.annotation.Nullable; import android.content.Context; import android.os.ShellCommand; import android.os.SystemClock; import android.os.Trace; import android.annotation.Nullable; 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 Loading @@ -49,18 +41,25 @@ import java.util.concurrent.BlockingQueue; */ class WindowTracing { /** * Maximum buffer size, currently defined as 512 KB * Size was experimentally defined to fit between 100 to 150 elements. */ private static final int WINDOW_TRACE_BUFFER_SIZE = 512 * 1024; 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 final WindowTraceBuffer.Builder mBufferBuilder; private WindowTraceBuffer mTraceBuffer; private boolean mEnabled; private volatile boolean mEnabledLockFree; WindowTracing(File file) { mTraceFile = file; mBufferBuilder = new WindowTraceBuffer.Builder() .setTraceFile(file) .setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE); } void startTrace(@Nullable PrintWriter pw) throws IOException { Loading @@ -69,15 +68,15 @@ class WindowTracing { return; } synchronized (mLock) { logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); mWriteQueue.clear(); mTraceFile.delete(); try (OutputStream os = new FileOutputStream(mTraceFile)) { mTraceFile.setReadable(true, false); ProtoOutputStream proto = new ProtoOutputStream(os); proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); proto.flush(); logAndPrintln(pw, "Start tracing to " + mBufferBuilder.getFile() + "."); if (mTraceBuffer != null) { try { mTraceBuffer.writeToDisk(); } catch (InterruptedException e) { logAndPrintln(pw, "Error: Unable to flush the previous buffer."); } } mTraceBuffer = mBufferBuilder.build(); mEnabled = mEnabledLockFree = true; } } Loading @@ -96,62 +95,37 @@ class WindowTracing { return; } synchronized (mLock) { logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); logAndPrintln(pw, "Stop tracing to " + mBufferBuilder.getFile() + ". Waiting for traces to flush."); mEnabled = mEnabledLockFree = false; while (!mWriteQueue.isEmpty()) { synchronized (mLock) { 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(); mTraceBuffer.writeToDisk(); } catch (IOException e) { Log.e(TAG, "Unable to write buffer to file", e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.e(TAG, "Unable to interrupt window tracing file write thread", e); } } logAndPrintln(pw, "Trace written to " + mTraceFile + "."); logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + "."); } } void appendTraceEntry(ProtoOutputStream proto) { private 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(); mTraceBuffer.add(proto); } catch (InterruptedException e) { Log.e(TAG, "Unable to add element to trace", 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(); } } Loading @@ -161,11 +135,7 @@ class WindowTracing { static WindowTracing createDefaultAndStartLooper(Context context) { File file = new File("/data/misc/wmtrace/wm_trace.pb"); WindowTracing windowTracing = new WindowTracing(file); if (!IS_USER){ new Thread(windowTracing::loop, "window_tracing").start(); } return windowTracing; return new WindowTracing(file); } int onShellCommand(ShellCommand shell, String cmd) { Loading
services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java 0 → 100644 +115 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.platform.test.annotations.Presubmit; import android.util.proto.ProtoOutputStream; import androidx.test.filters.SmallTest; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.util.Arrays; /** * Test class for {@link WindowTraceBuffer} and {@link WindowTraceQueueBuffer}. * * Build/Install/Run: * atest WmTests:WindowTraceBufferTest */ @SmallTest @Presubmit public class WindowTraceBufferTest { private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; private File mFile; @Before public void setUp() throws Exception { final Context testContext = getInstrumentation().getContext(); mFile = testContext.getFileStreamPath("tracing_test.dat"); mFile.delete(); } @After public void tearDown() throws Exception { mFile.delete(); } @Test public void testTraceQueueBuffer_addItem() throws Exception { ProtoOutputStream toWrite1 = getDummy(1); ProtoOutputStream toWrite2 = getDummy(2); ProtoOutputStream toWrite3 = getDummy(3); byte[] toWrite1Bytes = toWrite1.getBytes(); byte[] toWrite2Bytes = toWrite2.getBytes(); byte[] toWrite3Bytes = toWrite3.getBytes(); final int objectSize = toWrite1.getBytes().length; final int bufferCapacity = objectSize * 2; final WindowTraceBuffer buffer = buildQueueBuffer(bufferCapacity); buffer.add(toWrite1); assertTrue("First element should be in the list", buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes))); buffer.add(toWrite2); assertTrue("First element should be in the list", buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes))); assertTrue("Second element should be in the list", buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite2Bytes))); buffer.add(toWrite3); assertTrue("Third element should not be in the list", buffer.mBuffer.stream().noneMatch(p -> Arrays.equals(p, toWrite3Bytes))); assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2); assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity), buffer.mBufferSize, bufferCapacity); assertEquals("Buffer is full, available space should be 0", buffer.getAvailableSpace(), 0); } private ProtoOutputStream getDummy(int value) { ProtoOutputStream toWrite = new ProtoOutputStream(); toWrite.write(MAGIC_NUMBER, value); toWrite.flush(); return toWrite; } private WindowTraceBuffer buildQueueBuffer(int size) throws IOException { return new WindowTraceQueueBuffer(size, mFile, false); } }