blob: 0567960e05e4e76479913ab66917f984e3e16b59 [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.utils;
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 tracing and logging.
*/
public class TraceBuffer {
private final Object mBufferLock = new Object();
private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
private int mBufferUsedSize;
private int mBufferCapacity;
public TraceBuffer(int bufferCapacity) {
mBufferCapacity = bufferCapacity;
resetBuffer();
}
public int getAvailableSpace() {
return mBufferCapacity - mBufferUsedSize;
}
/**
* Returns buffer size.
*/
public int size() {
return mBuffer.size();
}
public 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.
*/
public 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 inside the encapsulatingProto..
*/
public void writeTraceToFile(File traceFile, ProtoOutputStream encapsulatingProto)
throws IOException {
synchronized (mBufferLock) {
traceFile.delete();
try (OutputStream os = new FileOutputStream(traceFile)) {
traceFile.setReadable(true /* readable */, false /* ownerOnly */);
os.write(encapsulatingProto.getBytes());
for (ProtoOutputStream protoOutputStream : mBuffer) {
encapsulatingProto = protoOutputStream;
byte[] protoBytes = encapsulatingProto.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
*/
public void resetBuffer() {
synchronized (mBufferLock) {
mBuffer.clear();
mBufferUsedSize = 0;
}
}
@VisibleForTesting
int getBufferSize() {
return mBufferUsedSize;
}
/**
* Returns the buffer status in human-readable form.
*/
public String getStatus() {
synchronized (mBufferLock) {
return "Buffer size: "
+ mBufferCapacity
+ " bytes"
+ "\n"
+ "Buffer usage: "
+ mBufferUsedSize
+ " bytes"
+ "\n"
+ "Elements in the buffer: "
+ mBuffer.size();
}
}
}