1 /*
2  * Copyright (C) 2020 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.crashtest.app;
18 
19 
20 import static com.android.nn.crashtest.app.CrashTestStatus.TestResult.HANG;
21 
22 import static java.util.concurrent.TimeUnit.MILLISECONDS;
23 
24 import android.annotation.SuppressLint;
25 import android.app.Activity;
26 import android.content.Intent;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.WindowManager;
32 import android.widget.Button;
33 import android.widget.TextView;
34 
35 import com.android.nn.benchmark.app.R;
36 import com.android.nn.benchmark.core.TestModels;
37 import com.android.nn.crashtest.core.CrashTestCoordinator;
38 import com.android.nn.crashtest.core.test.RunModelsInParallel;
39 
40 import java.time.Duration;
41 
42 
43 public class NNParallelTestActivity extends Activity {
44     public static final int SHUTDOWN_TIMEOUT = 20000;
45 
46     private static String TAG = "NNParallelTestActivity";
47 
48     public static final String EXTRA_TEST_DURATION_MILLIS = "duration";
49     public static final String EXTRA_THREAD_COUNT = "thread_count";
50     public static final String EXTRA_TEST_LIST = "test_list";
51     public static final String EXTRA_RUN_IN_SEPARATE_PROCESS = "run_in_separate_process";
52     public static final String EXTRA_TEST_NAME = "test_name";
53     public static final String EXTRA_ACCELERATOR_NAME = "accelerator_name";
54     public static final String EXTRA_IGNORE_UNSUPPORTED_MODELS = "ignore_unsupported_models";
55     public static final String EXTRA_RUN_MODEL_COMPILATION_ONLY = "run_model_compilation_only";
56     public static final String EXTRA_MEMORY_MAP_MODEL = "memory_map_model";
57     public static final String EXTRA_USE_NNAPI_SL = "use_nnapi_sl";
58     public static final String EXTRA_EXTRACT_NNAPI_SL = "extract_nnapi_sl";
59 
60     // Not using AtomicBoolean to have the concept of unset status
61     private CrashTestCoordinator mCoordinator;
62     private TextView mTestResultView;
63     private Button mStopTestButton;
64     private String mTestName;
65 
66     private final CrashTestStatus mTestStatus = new CrashTestStatus(this::showMessage);
67 
68     @SuppressLint("SetTextI18n")
69     @Override
onCreate(Bundle savedInstanceState)70     protected void onCreate(Bundle savedInstanceState) {
71         super.onCreate(savedInstanceState);
72         setContentView(R.layout.interruptable_test);
73         mTestResultView = findViewById(R.id.parallel_test_result);
74         mStopTestButton = findViewById(R.id.stop_test);
75         mStopTestButton.setEnabled(false);
76         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
77     }
78 
showMessage(String msg)79     protected void showMessage(String msg) {
80         runOnUiThread(() -> mTestResultView.append(msg));
81     }
82 
83 
84     @Override
onResume()85     protected void onResume() {
86         super.onResume();
87 
88         if (mTestStatus.isTestCompleted()) {
89             // test was completed before resuming
90             return;
91         }
92         if (mCoordinator != null) {
93             // test is already running.
94             return;
95         }
96 
97         final Intent intent = getIntent();
98 
99         final int[] testList = intent.getIntArrayExtra(EXTRA_TEST_LIST);
100 
101         final int threadCount = intent.getIntExtra(EXTRA_THREAD_COUNT, 10);
102         final long testDurationMillis = intent.getLongExtra(EXTRA_TEST_DURATION_MILLIS,
103                 1000 * 60 * 10);
104         final boolean runInSeparateProcess = intent.getBooleanExtra(EXTRA_RUN_IN_SEPARATE_PROCESS,
105                 true);
106         mTestName = intent.getStringExtra(EXTRA_TEST_NAME) != null
107                 ? intent.getStringExtra(EXTRA_TEST_NAME) : "no-name";
108 
109         mCoordinator = new CrashTestCoordinator(getApplicationContext());
110 
111         String acceleratorName = intent.getStringExtra(EXTRA_ACCELERATOR_NAME);
112         boolean ignoreUnsupportedModels = intent.getBooleanExtra(EXTRA_IGNORE_UNSUPPORTED_MODELS,
113                 false);
114         boolean mmapModel = intent.getBooleanExtra(EXTRA_MEMORY_MAP_MODEL, false);
115         boolean useNnapiSl = intent.getBooleanExtra(EXTRA_USE_NNAPI_SL, false);
116         boolean extractNnapiSl = intent.getBooleanExtra(EXTRA_EXTRACT_NNAPI_SL, false);
117 
118         final boolean runModelCompilationOnly = intent.getBooleanExtra(
119                 EXTRA_RUN_MODEL_COMPILATION_ONLY, false);
120 
121         mCoordinator.startTest(RunModelsInParallel.class,
122             RunModelsInParallel.intentInitializer(testList, threadCount,
123                 Duration.ofMillis(testDurationMillis), mTestName, acceleratorName,
124                 ignoreUnsupportedModels, runModelCompilationOnly, mmapModel,
125                 TestModels.getModelFilterRegex(), useNnapiSl, extractNnapiSl),
126             mTestStatus, runInSeparateProcess, mTestName);
127 
128         mStopTestButton.setEnabled(true);
129     }
130 
endTests()131     private void endTests() {
132         mCoordinator.shutdown();
133         mCoordinator = null;
134     }
135 
136     // This method blocks until the tests complete and returns true if all tests completed
137     // successfully
138     @SuppressLint("DefaultLocale")
testResult()139     public CrashTestStatus.TestResult testResult() {
140         try {
141             final Intent intent = getIntent();
142             final long testDurationMillis = intent.getLongExtra(EXTRA_TEST_DURATION_MILLIS,
143                     60 * 10);
144             // Giving the test a bit of time to wrap up
145             final long testResultTimeout = testDurationMillis + SHUTDOWN_TIMEOUT;
146             boolean completed = mTestStatus.waitForCompletion(testResultTimeout, MILLISECONDS);
147             if (!completed) {
148                 showMessage(String.format(
149                         "Ending test '%s' since test result collection timeout of %d "
150                                 + "millis is expired",
151                         mTestName, testResultTimeout));
152                 endTests();
153             }
154         } catch (InterruptedException ignored) {
155             Thread.currentThread().interrupt();
156         }
157 
158         // If no result is available, assuming HANG
159         mTestStatus.compareAndSetResult(null, HANG);
160         return mTestStatus.result();
161     }
162 
onStopTestClicked(View view)163     public void onStopTestClicked(View view) {
164         showMessage("Stopping tests");
165         endTests();
166     }
167 
168     /**
169      * Kills the process running the tests.
170      *
171      * @throws IllegalStateException if the method is called for an in-process test.
172      * @throws RemoteException       if the test service is not reachable
173      */
killTestProcess()174     public void killTestProcess() throws RemoteException {
175         final Intent intent = getIntent();
176 
177         final boolean runInSeparateProcess = intent.getBooleanExtra(EXTRA_RUN_IN_SEPARATE_PROCESS,
178                 true);
179 
180         if (!runInSeparateProcess) {
181             throw new IllegalStateException("Cannot kill the test process in an in-process test!");
182         }
183 
184         Log.i(TAG, "Asking coordinator to kill test process");
185         mCoordinator.killCrashTestService();
186     }
187 }
188