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