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
17package android.hardware.camera2.legacy;
18
19import android.os.SystemClock;
20import android.util.Log;
21
22import java.io.BufferedWriter;
23import java.io.FileWriter;
24import java.io.IOException;
25import java.util.ArrayList;
26import java.util.LinkedList;
27import 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 */
53class 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
239    public int getCompletedQueryCount() {
240        return mCompletedQueryCount;
241    }
242
243    @Override
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     */
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     */
260    private static native void nativeDeleteContext(long contextHandle);
261
262    /**
263     * Query whether the relevant Gl extensions are available for Gl timing
264     */
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     */
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     */
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     */
305    protected static native long nativeGetNextGlDuration(long contextHandle);
306
307
308}
309