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