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