1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.util; 18 19 import android.util.proto.ProtoOutputStream; 20 21 import com.android.internal.annotations.GuardedBy; 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import java.io.File; 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.io.OutputStream; 28 import java.util.ArrayDeque; 29 import java.util.Arrays; 30 import java.util.Queue; 31 import java.util.function.Consumer; 32 33 /** 34 * Buffer used for tracing and logging. 35 * 36 * @param <P> The class type of the proto provider 37 * @param <S> The proto class type of the encapsulating proto 38 * @param <T> The proto class type of the individual entry protos in the buffer 39 * 40 * {@hide} 41 */ 42 public class TraceBuffer<P, S extends P, T extends P> { 43 private final ProtoProvider<P, S, T> mProtoProvider; 44 @GuardedBy("this") 45 private final Queue<T> mBuffer = new ArrayDeque<>(); 46 private final Consumer mProtoDequeuedCallback; 47 @GuardedBy("this") 48 private int mBufferUsedSize; 49 @GuardedBy("this") 50 private int mBufferCapacity; 51 52 /** 53 * An interface to get protos from different sources (ie. fw-proto/proto-lite/nano-proto) for 54 * the trace buffer. 55 * 56 * @param <P> The class type of the proto provider 57 * @param <S> The proto class type of the encapsulating proto 58 * @param <T> The proto class type of the individual protos in the buffer 59 */ 60 public interface ProtoProvider<P, S extends P, T extends P> { 61 /** 62 * @return The size of the given proto. 63 */ getItemSize(P proto)64 int getItemSize(P proto); 65 66 /** 67 * @return The bytes of the given proto. 68 */ getBytes(P proto)69 byte[] getBytes(P proto); 70 71 /** 72 * Writes the given encapsulating proto and buffer of protos to the given output 73 * stream. 74 */ write(S encapsulatingProto, Queue<T> buffer, OutputStream os)75 void write(S encapsulatingProto, Queue<T> buffer, OutputStream os) throws IOException; 76 } 77 78 /** 79 * An implementation of the ProtoProvider that uses only the framework ProtoOutputStream. 80 */ 81 private static class ProtoOutputStreamProvider implements 82 ProtoProvider<ProtoOutputStream, ProtoOutputStream, ProtoOutputStream> { 83 @Override getItemSize(ProtoOutputStream proto)84 public int getItemSize(ProtoOutputStream proto) { 85 return proto.getRawSize(); 86 } 87 88 @Override getBytes(ProtoOutputStream proto)89 public byte[] getBytes(ProtoOutputStream proto) { 90 return proto.getBytes(); 91 } 92 93 @Override write(ProtoOutputStream encapsulatingProto, Queue<ProtoOutputStream> buffer, OutputStream os)94 public void write(ProtoOutputStream encapsulatingProto, Queue<ProtoOutputStream> buffer, 95 OutputStream os) throws IOException { 96 os.write(encapsulatingProto.getBytes()); 97 for (ProtoOutputStream protoOutputStream : buffer) { 98 byte[] protoBytes = protoOutputStream.getBytes(); 99 os.write(protoBytes); 100 } 101 } 102 } 103 TraceBuffer(int bufferCapacity)104 public TraceBuffer(int bufferCapacity) { 105 this(bufferCapacity, new ProtoOutputStreamProvider(), null); 106 } 107 TraceBuffer(int bufferCapacity, Consumer<T> protoDequeuedCallback)108 public TraceBuffer(int bufferCapacity, Consumer<T> protoDequeuedCallback) { 109 this(bufferCapacity, new ProtoOutputStreamProvider(), protoDequeuedCallback); 110 } 111 TraceBuffer(int bufferCapacity, ProtoProvider protoProvider, Consumer<T> protoDequeuedCallback)112 public TraceBuffer(int bufferCapacity, ProtoProvider protoProvider, 113 Consumer<T> protoDequeuedCallback) { 114 mBufferCapacity = bufferCapacity; 115 mProtoProvider = protoProvider; 116 mProtoDequeuedCallback = protoDequeuedCallback; 117 resetBuffer(); 118 } 119 getAvailableSpace()120 public synchronized int getAvailableSpace() { 121 return mBufferCapacity - mBufferUsedSize; 122 } 123 124 /** 125 * Returns buffer size. 126 */ size()127 public synchronized int size() { 128 return mBuffer.size(); 129 } 130 setCapacity(int capacity)131 public synchronized void setCapacity(int capacity) { 132 mBufferCapacity = capacity; 133 } 134 135 /** 136 * Inserts the specified element into this buffer. 137 * 138 * @param proto the element to add 139 * @throws IllegalStateException if the element cannot be added because it is larger 140 * than the buffer size. 141 */ add(T proto)142 public synchronized void add(T proto) { 143 int protoLength = mProtoProvider.getItemSize(proto); 144 if (protoLength > mBufferCapacity) { 145 throw new IllegalStateException("Trace object too large for the buffer. Buffer size:" 146 + mBufferCapacity + " Object size: " + protoLength); 147 } 148 discardOldest(protoLength); 149 mBuffer.add(proto); 150 mBufferUsedSize += protoLength; 151 } 152 153 @VisibleForTesting contains(byte[] other)154 public synchronized boolean contains(byte[] other) { 155 return mBuffer.stream() 156 .anyMatch(p -> Arrays.equals(mProtoProvider.getBytes(p), other)); 157 } 158 159 /** 160 * Writes the trace buffer to disk inside the encapsulatingProto. 161 */ writeTraceToFile(File traceFile, S encapsulatingProto)162 public synchronized void writeTraceToFile(File traceFile, S encapsulatingProto) 163 throws IOException { 164 traceFile.delete(); 165 try (OutputStream os = new FileOutputStream(traceFile)) { 166 traceFile.setReadable(true /* readable */, false /* ownerOnly */); 167 mProtoProvider.write(encapsulatingProto, mBuffer, os); 168 os.flush(); 169 } 170 } 171 172 /** 173 * Checks if the element can be added to the buffer. The element is already certain to be 174 * smaller than the overall buffer size. 175 * 176 * @param protoLength byte array representation of the Proto object to add 177 */ discardOldest(int protoLength)178 private void discardOldest(int protoLength) { 179 long availableSpace = getAvailableSpace(); 180 181 while (availableSpace < protoLength) { 182 183 P item = mBuffer.poll(); 184 if (item == null) { 185 throw new IllegalStateException("No element to discard from buffer"); 186 } 187 mBufferUsedSize -= mProtoProvider.getItemSize(item); 188 availableSpace = getAvailableSpace(); 189 190 if (mProtoDequeuedCallback != null) { 191 mProtoDequeuedCallback.accept(item); 192 } 193 } 194 } 195 196 /** 197 * Removes all elements from the buffer 198 */ resetBuffer()199 public synchronized void resetBuffer() { 200 if (mProtoDequeuedCallback != null) { 201 for (T item : mBuffer) { 202 mProtoDequeuedCallback.accept(item); 203 } 204 } 205 mBuffer.clear(); 206 mBufferUsedSize = 0; 207 } 208 209 @VisibleForTesting getBufferSize()210 public synchronized int getBufferSize() { 211 return mBufferUsedSize; 212 } 213 214 /** 215 * Returns the buffer status in human-readable form. 216 */ getStatus()217 public synchronized String getStatus() { 218 return "Buffer size: " + mBufferCapacity + " bytes" + "\n" 219 + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n" 220 + "Elements in the buffer: " + mBuffer.size(); 221 } 222 } 223