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 */ PerfMeasurement()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 */ PerfMeasurement(int maxQueries)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 */ isGlTimingSupported()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 */ dumpPerformanceData(String path)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 */ startTimer()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 */ stopTimer()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 */ addTimestamp(long timestamp)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 */ getNextGlDuration()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 */ getCompletedQueryCount()239 public int getCompletedQueryCount() { 240 return mCompletedQueryCount; 241 } 242 243 @Override finalize()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 */ nativeCreateContext(int maxQueryCount)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 */ nativeDeleteContext(long contextHandle)260 private static native void nativeDeleteContext(long contextHandle); 261 262 /** 263 * Query whether the relevant Gl extensions are available for Gl timing 264 */ nativeQuerySupport()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 */ nativeStartGlTimer(long contextHandle)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 */ nativeStopGlTimer(long contextHandle)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 */ nativeGetNextGlDuration(long contextHandle)305 protected static native long nativeGetNextGlDuration(long contextHandle); 306 307 308 } 309