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.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      */
     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