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.server.wm;
18 
19 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
20 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
21 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
22 
23 import android.util.proto.ProtoOutputStream;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.util.ArrayDeque;
32 import java.util.Arrays;
33 import java.util.Queue;
34 
35 /**
36  * Buffer used for window tracing.
37  */
38 class WindowTraceBuffer {
39     private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
40 
41     private final Object mBufferLock = new Object();
42 
43     private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
44     private int mBufferUsedSize;
45     private int mBufferCapacity;
46 
WindowTraceBuffer(int bufferCapacity)47     WindowTraceBuffer(int bufferCapacity) {
48         mBufferCapacity = bufferCapacity;
49         resetBuffer();
50     }
51 
getAvailableSpace()52     int getAvailableSpace() {
53         return mBufferCapacity - mBufferUsedSize;
54     }
55 
size()56     int size() {
57         return mBuffer.size();
58     }
59 
setCapacity(int capacity)60     void setCapacity(int capacity) {
61         mBufferCapacity = capacity;
62     }
63 
64     /**
65      * Inserts the specified element into this buffer.
66      *
67      * @param proto the element to add
68      * @throws IllegalStateException if the element cannot be added because it is larger
69      *                               than the buffer size.
70      */
add(ProtoOutputStream proto)71     void add(ProtoOutputStream proto) {
72         int protoLength = proto.getRawSize();
73         if (protoLength > mBufferCapacity) {
74             throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
75                     + mBufferCapacity + " Object size: " + protoLength);
76         }
77         synchronized (mBufferLock) {
78             discardOldest(protoLength);
79             mBuffer.add(proto);
80             mBufferUsedSize += protoLength;
81             mBufferLock.notify();
82         }
83     }
84 
contains(byte[] other)85     boolean contains(byte[] other) {
86         return mBuffer.stream()
87                 .anyMatch(p -> Arrays.equals(p.getBytes(), other));
88     }
89 
90     /**
91      * Writes the trace buffer to disk.
92      */
writeTraceToFile(File traceFile)93     void writeTraceToFile(File traceFile) throws IOException {
94         synchronized (mBufferLock) {
95             traceFile.delete();
96             try (OutputStream os = new FileOutputStream(traceFile)) {
97                 traceFile.setReadable(true /* readable */, false /* ownerOnly */);
98                 ProtoOutputStream proto = new ProtoOutputStream();
99                 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
100                 os.write(proto.getBytes());
101                 for (ProtoOutputStream protoOutputStream : mBuffer) {
102                     proto = protoOutputStream;
103                     byte[] protoBytes = proto.getBytes();
104                     os.write(protoBytes);
105                 }
106                 os.flush();
107             }
108         }
109     }
110 
111     /**
112      * Checks if the element can be added to the buffer. The element is already certain to be
113      * smaller than the overall buffer size.
114      *
115      * @param protoLength byte array representation of the Proto object to add
116      */
discardOldest(int protoLength)117     private void discardOldest(int protoLength) {
118         long availableSpace = getAvailableSpace();
119 
120         while (availableSpace < protoLength) {
121 
122             ProtoOutputStream item = mBuffer.poll();
123             if (item == null) {
124                 throw new IllegalStateException("No element to discard from buffer");
125             }
126             mBufferUsedSize -= item.getRawSize();
127             availableSpace = getAvailableSpace();
128         }
129     }
130 
131     /**
132      * Removes all elements form the buffer
133      */
resetBuffer()134     void resetBuffer() {
135         synchronized (mBufferLock) {
136             mBuffer.clear();
137             mBufferUsedSize = 0;
138         }
139     }
140 
141     @VisibleForTesting
getBufferSize()142     int getBufferSize() {
143         return mBufferUsedSize;
144     }
145 
getStatus()146     String getStatus() {
147         synchronized (mBufferLock) {
148             return "Buffer size: "
149                     + mBufferCapacity
150                     + " bytes"
151                     + "\n"
152                     + "Buffer usage: "
153                     + mBufferUsedSize
154                     + " bytes"
155                     + "\n"
156                     + "Elements in the buffer: "
157                     + mBuffer.size();
158         }
159     }
160 }
161