/* * 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.internal.util; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; 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; import java.util.function.Consumer; /** * Buffer used for tracing and logging. * * @param

The class type of the proto provider * @param The proto class type of the encapsulating proto * @param The proto class type of the individual entry protos in the buffer * * {@hide} */ public class TraceBuffer { private final ProtoProvider mProtoProvider; @GuardedBy("this") private final Queue mBuffer = new ArrayDeque<>(); private final Consumer mProtoDequeuedCallback; @GuardedBy("this") private int mBufferUsedSize; @GuardedBy("this") private int mBufferCapacity; /** * An interface to get protos from different sources (ie. fw-proto/proto-lite/nano-proto) for * the trace buffer. * * @param

The class type of the proto provider * @param The proto class type of the encapsulating proto * @param The proto class type of the individual protos in the buffer */ public interface ProtoProvider { /** * @return The size of the given proto. */ int getItemSize(P proto); /** * @return The bytes of the given proto. */ byte[] getBytes(P proto); /** * Writes the given encapsulating proto and buffer of protos to the given output * stream. */ void write(S encapsulatingProto, Queue buffer, OutputStream os) throws IOException; } /** * An implementation of the ProtoProvider that uses only the framework ProtoOutputStream. */ private static class ProtoOutputStreamProvider implements ProtoProvider { @Override public int getItemSize(ProtoOutputStream proto) { return proto.getRawSize(); } @Override public byte[] getBytes(ProtoOutputStream proto) { return proto.getBytes(); } @Override public void write(ProtoOutputStream encapsulatingProto, Queue buffer, OutputStream os) throws IOException { os.write(encapsulatingProto.getBytes()); for (ProtoOutputStream protoOutputStream : buffer) { byte[] protoBytes = protoOutputStream.getBytes(); os.write(protoBytes); } } } public TraceBuffer(int bufferCapacity) { this(bufferCapacity, new ProtoOutputStreamProvider(), null); } public TraceBuffer(int bufferCapacity, Consumer protoDequeuedCallback) { this(bufferCapacity, new ProtoOutputStreamProvider(), protoDequeuedCallback); } public TraceBuffer(int bufferCapacity, ProtoProvider protoProvider, Consumer protoDequeuedCallback) { mBufferCapacity = bufferCapacity; mProtoProvider = protoProvider; mProtoDequeuedCallback = protoDequeuedCallback; resetBuffer(); } public synchronized int getAvailableSpace() { return mBufferCapacity - mBufferUsedSize; } /** * Returns buffer size. */ public synchronized int size() { return mBuffer.size(); } public synchronized 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 synchronized void add(T proto) { int protoLength = mProtoProvider.getItemSize(proto); if (protoLength > mBufferCapacity) { throw new IllegalStateException("Trace object too large for the buffer. Buffer size:" + mBufferCapacity + " Object size: " + protoLength); } discardOldest(protoLength); mBuffer.add(proto); mBufferUsedSize += protoLength; } @VisibleForTesting public synchronized boolean contains(byte[] other) { return mBuffer.stream() .anyMatch(p -> Arrays.equals(mProtoProvider.getBytes(p), other)); } /** * Writes the trace buffer to disk inside the encapsulatingProto. */ public synchronized void writeTraceToFile(File traceFile, S encapsulatingProto) throws IOException { traceFile.delete(); try (OutputStream os = new FileOutputStream(traceFile)) { traceFile.setReadable(true /* readable */, false /* ownerOnly */); mProtoProvider.write(encapsulatingProto, mBuffer, os); 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) { P item = mBuffer.poll(); if (item == null) { throw new IllegalStateException("No element to discard from buffer"); } mBufferUsedSize -= mProtoProvider.getItemSize(item); availableSpace = getAvailableSpace(); if (mProtoDequeuedCallback != null) { mProtoDequeuedCallback.accept(item); } } } /** * Removes all elements from the buffer */ public synchronized void resetBuffer() { if (mProtoDequeuedCallback != null) { for (T item : mBuffer) { mProtoDequeuedCallback.accept(item); } } mBuffer.clear(); mBufferUsedSize = 0; } @VisibleForTesting public synchronized int getBufferSize() { return mBufferUsedSize; } /** * Returns the buffer status in human-readable form. */ public synchronized String getStatus() { return "Buffer size: " + mBufferCapacity + " bytes" + "\n" + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n" + "Elements in the buffer: " + mBuffer.size(); } }