| /* |
| * 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 com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayDeque; |
| import java.util.Arrays; |
| import java.util.Queue; |
| |
| /** |
| * 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 mBufferLock = new Object(); |
| final Queue<byte[]> mBuffer = new ArrayDeque<>(); |
| final File mTraceFile; |
| int mBufferSize; |
| private final int mBufferCapacity; |
| |
| WindowTraceBuffer(int size, File traceFile) throws IOException { |
| mBufferCapacity = size; |
| mTraceFile = traceFile; |
| |
| initTraceFile(); |
| } |
| |
| int getAvailableSpace() { |
| return mBufferCapacity - mBufferSize; |
| } |
| |
| /** |
| * Inserts the specified element into this buffer. |
| * |
| * @param proto the element to add |
| * @throws IllegalStateException if the element cannot be added because it is larger |
| * than the buffer size. |
| */ |
| void add(ProtoOutputStream proto) { |
| 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 (mBufferLock) { |
| boolean canAdd = canAdd(protoLength); |
| if (canAdd) { |
| mBuffer.offer(protoBytes); |
| mBufferSize += protoLength; |
| } |
| mBufferLock.notify(); |
| } |
| } |
| |
| /** |
| * Stops the buffer execution and flush all buffer content to the disk. |
| * |
| * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile} |
| */ |
| void dump() throws IOException, InterruptedException { |
| try { |
| Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFile"); |
| writeTraceToFile(); |
| } finally { |
| Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); |
| } |
| } |
| |
| @VisibleForTesting |
| boolean contains(byte[] other) { |
| return mBuffer.stream() |
| .anyMatch(p -> Arrays.equals(p, other)); |
| } |
| |
| 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 protoLength byte array representation of the Proto object to add |
| * @return {@code true} if the element can be added to the buffer or not |
| */ |
| abstract boolean canAdd(int protoLength); |
| |
| /** |
| * Flush all buffer content to the disk. |
| * |
| * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile} |
| */ |
| abstract void writeTraceToFile() throws IOException, InterruptedException; |
| |
| /** |
| * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceRingBuffer} for |
| * continuous mode or a {@link WindowTraceQueueBuffer} otherwise |
| */ |
| static class Builder { |
| private boolean mContinuous; |
| private File mTraceFile; |
| private int mBufferCapacity; |
| |
| Builder setContinuousMode(boolean continuous) { |
| mContinuous = continuous; |
| return this; |
| } |
| |
| 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."); |
| } |
| |
| if (mContinuous) { |
| return new WindowTraceRingBuffer(mBufferCapacity, mTraceFile); |
| } else { |
| return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile); |
| } |
| } |
| } |
| } |