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