• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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