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