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