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