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