Home | History | Annotate | Download | only in legacy
      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      */
     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