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.File;
24import java.io.FileWriter;
25import java.io.IOException;
26import java.util.ArrayList;
27import java.util.LinkedList;
28import 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 */
54class 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
240    public int getCompletedQueryCount() {
241        return mCompletedQueryCount;
242    }
243
244    @Override
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     */
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     */
261    private static native void nativeDeleteContext(long contextHandle);
262
263    /**
264     * Query whether the relevant Gl extensions are available for Gl timing
265     */
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     */
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     */
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     */
306    protected static native long nativeGetNextGlDuration(long contextHandle);
307
308
309}
310