blob: 8c65884a4d89d9ae26107133219a844100cdcee9 [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.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.
*/
class WindowTraceBuffer {
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
private final Object mBufferLock = new Object();
private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
private int mBufferUsedSize;
private int mBufferCapacity;
WindowTraceBuffer(int bufferCapacity) {
mBufferCapacity = bufferCapacity;
resetBuffer();
}
int getAvailableSpace() {
return mBufferCapacity - mBufferUsedSize;
}
int size() {
return mBuffer.size();
}
void setCapacity(int capacity) {
mBufferCapacity = capacity;
}
/**
* 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) {
int protoLength = proto.getRawSize();
if (protoLength > mBufferCapacity) {
throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
+ mBufferCapacity + " Object size: " + protoLength);
}
synchronized (mBufferLock) {
discardOldest(protoLength);
mBuffer.add(proto);
mBufferUsedSize += protoLength;
mBufferLock.notify();
}
}
boolean contains(byte[] other) {
return mBuffer.stream()
.anyMatch(p -> Arrays.equals(p.getBytes(), other));
}
/**
* Writes the trace buffer to disk.
*/
void writeTraceToFile(File traceFile) throws IOException {
synchronized (mBufferLock) {
traceFile.delete();
try (OutputStream os = new FileOutputStream(traceFile)) {
traceFile.setReadable(true /* readable */, false /* ownerOnly */);
ProtoOutputStream proto = new ProtoOutputStream();
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
os.write(proto.getBytes());
for (ProtoOutputStream protoOutputStream : mBuffer) {
proto = protoOutputStream;
byte[] protoBytes = proto.getBytes();
os.write(protoBytes);
}
os.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
*/
private void discardOldest(int protoLength) {
long availableSpace = getAvailableSpace();
while (availableSpace < protoLength) {
ProtoOutputStream item = mBuffer.poll();
if (item == null) {
throw new IllegalStateException("No element to discard from buffer");
}
mBufferUsedSize -= item.getRawSize();
availableSpace = getAvailableSpace();
}
}
/**
* Removes all elements form the buffer
*/
void resetBuffer() {
synchronized (mBufferLock) {
mBuffer.clear();
mBufferUsedSize = 0;
}
}
@VisibleForTesting
int getBufferSize() {
return mBufferUsedSize;
}
String getStatus() {
synchronized (mBufferLock) {
return "Buffer size: "
+ mBufferCapacity
+ " bytes"
+ "\n"
+ "Buffer usage: "
+ mBufferUsedSize
+ " bytes"
+ "\n"
+ "Elements in the buffer: "
+ mBuffer.size();
}
}
}