1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
|
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.camera2.legacy;
import android.os.SystemClock;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
* GPU and CPU performance measurement for the legacy implementation.
*
* <p>Measures CPU and GPU processing duration for a set of operations, and dumps
* the results into a file.</p>
*
* <p>Rough usage:
* <pre>
* {@code
* <set up workload>
* <start long-running workload>
* mPerfMeasurement.startTimer();
* ...render a frame...
* mPerfMeasurement.stopTimer();
* <end workload>
* mPerfMeasurement.dumpPerformanceData("/sdcard/my_data.txt");
* }
* </pre>
* </p>
*
* <p>All calls to this object must be made within the same thread, and the same GL context.
* PerfMeasurement cannot be used outside of a GL context. The only exception is
* dumpPerformanceData, which can be called outside of a valid GL context.</p>
*/
class PerfMeasurement {
private static final String TAG = "PerfMeasurement";
public static final int DEFAULT_MAX_QUERIES = 3;
private final long mNativeContext;
private int mCompletedQueryCount = 0;
/**
* Values for completed measurements
*/
private ArrayList<Long> mCollectedGpuDurations = new ArrayList<>();
private ArrayList<Long> mCollectedCpuDurations = new ArrayList<>();
private ArrayList<Long> mCollectedTimestamps = new ArrayList<>();
/**
* Values for in-progress measurements (waiting for async GPU results)
*/
private Queue<Long> mTimestampQueue = new LinkedList<>();
private Queue<Long> mCpuDurationsQueue = new LinkedList<>();
private long mStartTimeNs;
/**
* The value returned by {@link #nativeGetNextGlDuration} if no new timing
* measurement is available since the last call.
*/
private static final long NO_DURATION_YET = -1l;
/**
* The value returned by {@link #nativeGetNextGlDuration} if timing failed for
* the next timing interval
*/
private static final long FAILED_TIMING = -2l;
/**
* Create a performance measurement object with a maximum of {@value #DEFAULT_MAX_QUERIES}
* in-progess queries.
*/
public PerfMeasurement() {
mNativeContext = nativeCreateContext(DEFAULT_MAX_QUERIES);
}
/**
* Create a performance measurement object with maxQueries as the maximum number of
* in-progress queries.
*
* @param maxQueries maximum in-progress queries, must be larger than 0.
* @throws IllegalArgumentException if maxQueries is less than 1.
*/
public PerfMeasurement(int maxQueries) {
if (maxQueries < 1) throw new IllegalArgumentException("maxQueries is less than 1");
mNativeContext = nativeCreateContext(maxQueries);
}
/**
* Returns true if the Gl timing methods will work, false otherwise.
*
* <p>Must be called within a valid GL context.</p>
*/
public static boolean isGlTimingSupported() {
return nativeQuerySupport();
}
/**
* Dump collected data to file, and clear the stored data.
*
* <p>
* Format is a simple csv-like text file with a header,
* followed by a 3-column list of values in nanoseconds:
* <pre>
* timestamp gpu_duration cpu_duration
* <long> <long> <long>
* <long> <long> <long>
* <long> <long> <long>
* ....
* </pre>
* </p>
*/
public void dumpPerformanceData(String path) {
try (BufferedWriter dump = new BufferedWriter(new FileWriter(path))) {
dump.write("timestamp gpu_duration cpu_duration\n");
for (int i = 0; i < mCollectedGpuDurations.size(); i++) {
dump.write(String.format("%d %d %d\n",
mCollectedTimestamps.get(i),
mCollectedGpuDurations.get(i),
mCollectedCpuDurations.get(i)));
}
mCollectedTimestamps.clear();
mCollectedGpuDurations.clear();
mCollectedCpuDurations.clear();
} catch (IOException e) {
Log.e(TAG, "Error writing data dump to " + path + ":" + e);
}
}
/**
* Start a GPU/CPU timing measurement.
*
* <p>Call before starting a rendering pass. Only one timing measurement can be active at once,
* so {@link #stopTimer} must be called before the next call to this method.</p>
*
* @throws IllegalStateException if the maximum number of queries are in progress already,
* or the method is called multiple times in a row, or there is
* a GPU error.
*/
public void startTimer() {
nativeStartGlTimer(mNativeContext);
mStartTimeNs = SystemClock.elapsedRealtimeNanos();
}
/**
* Finish a GPU/CPU timing measurement.
*
* <p>Call after finishing all the drawing for a rendering pass. Only one timing measurement can
* be active at once, so {@link #startTimer} must be called before the next call to this
* method.</p>
*
* @throws IllegalStateException if no GL timer is currently started, or there is a GPU
* error.
*/
public void stopTimer() {
// Complete CPU timing
long endTimeNs = SystemClock.elapsedRealtimeNanos();
mCpuDurationsQueue.add(endTimeNs - mStartTimeNs);
// Complete GL timing
nativeStopGlTimer(mNativeContext);
// Poll to see if GL timing results have arrived; if so
// store the results for a frame
long duration = getNextGlDuration();
if (duration > 0) {
mCollectedGpuDurations.add(duration);
mCollectedTimestamps.add(mTimestampQueue.isEmpty() ?
NO_DURATION_YET : mTimestampQueue.poll());
mCollectedCpuDurations.add(mCpuDurationsQueue.isEmpty() ?
NO_DURATION_YET : mCpuDurationsQueue.poll());
}
if (duration == FAILED_TIMING) {
// Discard timestamp and CPU measurement since GPU measurement failed
if (!mTimestampQueue.isEmpty()) {
mTimestampQueue.poll();
}
if (!mCpuDurationsQueue.isEmpty()) {
mCpuDurationsQueue.poll();
}
}
}
/**
* Add a timestamp to a timing measurement. These are queued up and matched to completed
* workload measurements as they become available.
*/
public void addTimestamp(long timestamp) {
mTimestampQueue.add(timestamp);
}
/**
* Get the next available GPU timing measurement.
*
* <p>Since the GPU works asynchronously, the results of a single start/stopGlTimer measurement
* will only be available some time after the {@link #stopTimer} call is made. Poll this method
* until the result becomes available. If multiple start/endTimer measurements are made in a
* row, the results will be available in FIFO order.</p>
*
* @return The measured duration of the GPU workload for the next pending query, or
* {@link #NO_DURATION_YET} if no queries are pending or the next pending query has not
* yet finished, or {@link #FAILED_TIMING} if the GPU was unable to complete the
* measurement.
*
* @throws IllegalStateException If there is a GPU error.
*
*/
private long getNextGlDuration() {
long duration = nativeGetNextGlDuration(mNativeContext);
if (duration > 0) {
mCompletedQueryCount++;
}
return duration;
}
/**
* Returns the number of measurements so far that returned a valid duration
* measurement.
*/
public int getCompletedQueryCount() {
return mCompletedQueryCount;
}
@Override
protected void finalize() {
nativeDeleteContext(mNativeContext);
}
/**
* Create a native performance measurement context.
*
* @param maxQueryCount maximum in-progress queries; must be >= 1.
*/
private static native long nativeCreateContext(int maxQueryCount);
/**
* Delete the native context.
*
* <p>Not safe to call more than once.</p>
*/
private static native void nativeDeleteContext(long contextHandle);
/**
* Query whether the relevant Gl extensions are available for Gl timing
*/
private static native boolean nativeQuerySupport();
/**
* Start a GL timing section.
*
* <p>All GL commands between this method and the next {@link #nativeEndGlTimer} will be
* included in the timing.</p>
*
* <p>Must be called from the same thread as calls to {@link #nativeEndGlTimer} and
* {@link #nativeGetNextGlDuration}.</p>
*
* @throws IllegalStateException if a GL error occurs or start is called repeatedly.
*/
protected static native void nativeStartGlTimer(long contextHandle);
/**
* Finish a GL timing section.
*
* <p>Some time after this call returns, the time the GPU took to
* execute all work submitted between the latest {@link #nativeStartGlTimer} and
* this call, will become available from calling {@link #nativeGetNextGlDuration}.</p>
*
* <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
* {@link #nativeGetNextGlDuration}.</p>
*
* @throws IllegalStateException if a GL error occurs or stop is called before start
*/
protected static native void nativeStopGlTimer(long contextHandle);
/**
* Get the next available GL duration measurement, in nanoseconds.
*
* <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
* {@link #nativeEndGlTimer}.</p>
*
* @return the next GL duration measurement, or {@link #NO_DURATION_YET} if
* no new measurement is available, or {@link #FAILED_TIMING} if timing
* failed for the next duration measurement.
* @throws IllegalStateException if a GL error occurs
*/
protected static native long nativeGetNextGlDuration(long contextHandle);
}
|