1 /* 2 * Copyright (C) 2018 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 com.android.nn.benchmark.app; 18 19 import static org.junit.Assume.assumeTrue; 20 21 import com.android.nn.benchmark.core.NNTestBase; 22 import com.android.nn.benchmark.core.NnApiDelegationFailure; 23 import com.android.nn.benchmark.core.Processor; 24 25 import android.app.Activity; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.os.BatteryManager; 31 import android.os.Trace; 32 import android.test.ActivityInstrumentationTestCase2; 33 import android.util.Log; 34 35 import androidx.test.InstrumentationRegistry; 36 37 import com.android.nn.benchmark.core.BenchmarkException; 38 import com.android.nn.benchmark.core.BenchmarkResult; 39 import com.android.nn.benchmark.core.TestModels; 40 import com.android.nn.benchmark.core.TestModels.TestModelEntry; 41 42 import java.util.stream.Collectors; 43 import org.junit.After; 44 import org.junit.Before; 45 import org.junit.runner.RunWith; 46 import org.junit.runners.Parameterized; 47 import org.junit.runners.Parameterized.Parameters; 48 49 import java.io.IOException; 50 import java.util.List; 51 import java.util.concurrent.CountDownLatch; 52 53 /** 54 * Benchmark test-case super-class. 55 * 56 * Helper code for managing NNAPI/NNAPI-less benchamarks. 57 */ 58 @RunWith(Parameterized.class) 59 public class BenchmarkTestBase extends ActivityInstrumentationTestCase2<NNBenchmark> { 60 61 // Only run 1 iteration now to fit the MediumTest time requirement. 62 // One iteration means running the tests continuous for 1s. 63 private NNBenchmark mActivity; 64 protected final TestModelEntry mModel; 65 protected final String mAcceleratorName; 66 67 // The default 0.3s warmup and 1.0s runtime give reasonably repeatable results (run-to-run 68 // variability of ~20%) when run under performance settings (fixed CPU cores enabled and at 69 // fixed frequency). The continuous build is not allowed to take much more than 1s so we 70 // can't change the defaults for @MediumTest. 71 protected static final float WARMUP_SHORT_SECONDS = 0.3f; 72 protected static final float RUNTIME_SHORT_SECONDS = 1.f; 73 74 // For running like a normal user-initiated app, the variability for 0.3s/1.0s is easily 3x. 75 // With 2s/10s it's 20-50%. This @LargeTest allows running with these timings. 76 protected static final float WARMUP_REPEATABLE_SECONDS = 2.f; 77 protected static final float RUNTIME_REPEATABLE_SECONDS = 10.f; 78 79 // For running a complete dataset, the run should complete under 5 minutes. 80 protected static final float COMPLETE_SET_TIMEOUT_SECOND = 300.f; 81 82 // For running compilation benchmarks. 83 protected static final float COMPILATION_WARMUP_SECONDS = 2.f; 84 protected static final float COMPILATION_RUNTIME_SECONDS = 10.f; 85 protected static final int COMPILATION_MAX_ITERATIONS = 100; 86 BenchmarkTestBase(TestModelEntry model, String acceleratorName)87 public BenchmarkTestBase(TestModelEntry model, String acceleratorName) { 88 super(NNBenchmark.class); 89 mModel = model; 90 mAcceleratorName = acceleratorName; 91 } 92 setUseNNApi(boolean useNNApi)93 protected void setUseNNApi(boolean useNNApi) { 94 mActivity.setUseNNApi(useNNApi); 95 if (useNNApi) { 96 final boolean useNnApiSupportLibrary = NNTestBase.shouldUseNnApiSupportLibrary(); 97 final boolean extractNnApiSupportLibrary = NNTestBase.shouldExtractNnApiSupportLibrary(); 98 final String nnApiSupportLibraryVendor = NNTestBase.getNnApiSupportLibraryVendor(); 99 Log.i(NNBenchmark.TAG, "Configuring usage of NNAPI SL to " + useNnApiSupportLibrary); 100 mActivity.setUseNnApiSupportLibrary(useNnApiSupportLibrary); 101 mActivity.setExtractNnApiSupportLibrary(extractNnApiSupportLibrary); 102 mActivity.setNnApiSupportLibraryVendor(nnApiSupportLibraryVendor); 103 } 104 } 105 setNnApiAcceleratorName(String acceleratorName)106 protected void setNnApiAcceleratorName(String acceleratorName) { 107 mActivity.setNnApiAcceleratorName(acceleratorName); 108 } 109 setCompleteInputSet(boolean completeInputSet)110 protected void setCompleteInputSet(boolean completeInputSet) { 111 mActivity.setCompleteInputSet(completeInputSet); 112 } 113 enableCompilationCachingBenchmarks()114 protected void enableCompilationCachingBenchmarks() { 115 mActivity.enableCompilationCachingBenchmarks(COMPILATION_WARMUP_SECONDS, 116 COMPILATION_RUNTIME_SECONDS, COMPILATION_MAX_ITERATIONS); 117 } 118 isModelSupported()119 private boolean isModelSupported() { 120 try { 121 return Processor.isTestModelSupportedByAccelerator(mActivity, mModel, mAcceleratorName); 122 } catch (NnApiDelegationFailure delegationFailure) { 123 throw new Error( 124 String.format("Failure checking if model %s is supported on accelerator %s ", 125 mModel.mModelName, mAcceleratorName), delegationFailure); 126 } 127 } 128 129 // Initialize the parameter for ImageProcessingActivityJB. prepareTest()130 protected void prepareTest() { 131 injectInstrumentation(InstrumentationRegistry.getInstrumentation()); 132 mActivity = getActivity(); 133 mActivity.prepareInstrumentationTest(); 134 if (mAcceleratorName != null) { 135 assumeTrue(isModelSupported()); 136 mActivity.setNnApiAcceleratorName(mAcceleratorName); 137 } 138 setUseNNApi(true); 139 } 140 waitUntilCharged()141 public void waitUntilCharged() { 142 BenchmarkTestBase.waitUntilCharged(mActivity, -1); 143 } 144 waitUntilCharged(Context context, int minChargeLevel)145 public static void waitUntilCharged(Context context, int minChargeLevel) { 146 Log.v(NNBenchmark.TAG, "Waiting for the device to charge"); 147 148 final CountDownLatch chargedLatch = new CountDownLatch(1); 149 BroadcastReceiver receiver = new BroadcastReceiver() { 150 @Override 151 public void onReceive(Context context, Intent intent) { 152 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); 153 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); 154 int percentage = level * 100 / scale; 155 if (minChargeLevel > 0 && minChargeLevel < 100) { 156 if (percentage >= minChargeLevel) { 157 Log.v(NNBenchmark.TAG, 158 String.format( 159 "Battery level: %d%% is greater than requested %d%%. " 160 + "Considering the device ready.", 161 percentage, minChargeLevel)); 162 163 chargedLatch.countDown(); 164 return; 165 } 166 } 167 168 Log.v(NNBenchmark.TAG, String.format("Battery level: %d%%", percentage)); 169 170 int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); 171 if (status == BatteryManager.BATTERY_STATUS_FULL) { 172 chargedLatch.countDown(); 173 } else if (status != BatteryManager.BATTERY_STATUS_CHARGING) { 174 Log.e(NNBenchmark.TAG, 175 String.format("Device is not charging, status is %d", status)); 176 } 177 } 178 }; 179 180 context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 181 try { 182 chargedLatch.await(); 183 } catch (InterruptedException ignored) { 184 Thread.currentThread().interrupt(); 185 } 186 187 context.unregisterReceiver(receiver); 188 } 189 190 @Override 191 @Before setUp()192 public void setUp() throws Exception { 193 super.setUp(); 194 prepareTest(); 195 setActivityInitialTouchMode(false); 196 } 197 198 @Override 199 @After tearDown()200 public void tearDown() throws Exception { 201 super.tearDown(); 202 } 203 204 interface Joinable extends Runnable { 205 // Syncrhonises the caller with the completion of the current action join()206 void join(); 207 } 208 209 class TestAction implements Joinable { 210 211 private final TestModelEntry mTestModel; 212 private final float mMaxWarmupTimeSeconds; 213 private final float mMaxRunTimeSeconds; 214 private final CountDownLatch actionComplete; 215 private final boolean mSampleResults; 216 217 BenchmarkResult mResult; 218 Throwable mException; 219 TestAction(TestModelEntry testName, float maxWarmupTimeSeconds, float maxRunTimeSeconds)220 public TestAction(TestModelEntry testName, float maxWarmupTimeSeconds, 221 float maxRunTimeSeconds) { 222 this(testName, maxWarmupTimeSeconds, maxRunTimeSeconds, false); 223 } 224 TestAction(TestModelEntry testName, float maxWarmupTimeSeconds, float maxRunTimeSeconds, boolean sampleResults)225 public TestAction(TestModelEntry testName, float maxWarmupTimeSeconds, 226 float maxRunTimeSeconds, boolean sampleResults) { 227 mTestModel = testName; 228 mMaxWarmupTimeSeconds = maxWarmupTimeSeconds; 229 mMaxRunTimeSeconds = maxRunTimeSeconds; 230 mSampleResults = sampleResults; 231 actionComplete = new CountDownLatch(1); 232 } 233 run()234 public void run() { 235 Log.v(NNBenchmark.TAG, String.format( 236 "Starting benchmark for test '%s' running for max %f seconds", 237 mTestModel.mTestName, 238 mMaxRunTimeSeconds)); 239 try { 240 mResult = mActivity.runSynchronously( 241 mTestModel, mMaxWarmupTimeSeconds, mMaxRunTimeSeconds, mSampleResults); 242 } catch (BenchmarkException | IOException e) { 243 mException = e; 244 Log.e(NNBenchmark.TAG, 245 String.format("Error running Benchmark for test '%s'", mTestModel), e); 246 } catch (Throwable e) { 247 mException = e; 248 Log.e(NNBenchmark.TAG, 249 String.format("Failure running Benchmark for test '%s'!!", mTestModel), e); 250 throw e; 251 } finally { 252 actionComplete.countDown(); 253 } 254 } 255 getBenchmark()256 public BenchmarkResult getBenchmark() { 257 if (mException != null) { 258 throw new Error("run failed", mException); 259 } 260 return mResult; 261 } 262 263 @Override join()264 public void join() { 265 try { 266 actionComplete.await(); 267 } catch (InterruptedException e) { 268 Thread.currentThread().interrupt(); 269 Log.v(NNBenchmark.TAG, "Interrupted while waiting for action running", e); 270 } 271 } 272 } 273 274 // Set the benchmark thread to run on ui thread 275 // Synchronized the thread such that the test will wait for the benchmark thread to finish runOnUiThread(Joinable action)276 public void runOnUiThread(Joinable action) { 277 mActivity.runOnUiThread(action); 278 action.join(); 279 } 280 runTest(TestAction ta, String testName)281 public void runTest(TestAction ta, String testName) { 282 float sum = 0; 283 // For NNAPI systrace usage documentation, see 284 // frameworks/ml/nn/common/include/Tracing.h. 285 final String traceName = "[NN_LA_PO]" + testName; 286 try { 287 Trace.beginSection(traceName); 288 Log.i(NNBenchmark.TAG, "Starting test " + testName); 289 runOnUiThread(ta); 290 Log.i(NNBenchmark.TAG, "Test " + testName + " completed"); 291 } finally { 292 Trace.endSection(); 293 } 294 BenchmarkResult bmValue = ta.getBenchmark(); 295 296 // post result to INSTRUMENTATION_STATUS 297 getInstrumentation().sendStatus(Activity.RESULT_OK, bmValue.toBundle(testName)); 298 } 299 300 @Parameters(name = "{0} model on accelerator {1}") modelsOnAccelerators()301 public static List<Object[]> modelsOnAccelerators() { 302 Log.d(NNBenchmark.TAG, "Calculating list of models"); 303 List<Object[]> result = AcceleratorSpecificTestSupport.maybeAddAcceleratorsToTestConfig( 304 TestModels.modelsList().stream().map(model -> new Object[] {model}).collect(Collectors.toList()) 305 ); 306 Log.d(NNBenchmark.TAG, "Returning list of models of size " + result.size()); 307 return result; 308 } 309 } 310