blob: 2f672f24cc4f5f9e19acacc3339bb1236bc012fe [file] [log] [blame]
/*
* 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);
}
}
}
}