blob: 936ee85697b8d5c3e2b8f488473425278d9a2fa5 [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 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);
}
}
}