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