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         if (mCoordinator != null) {
90             // test is already running.
91             return;
92         }
93 
94         final Intent intent = getIntent();
95 
96         final int[] testList = intent.getIntArrayExtra(EXTRA_TEST_LIST);
97 
98         final int threadCount = intent.getIntExtra(EXTRA_THREAD_COUNT, 10);
99         final long testDurationMillis = intent.getLongExtra(EXTRA_TEST_DURATION_MILLIS,
100                 1000 * 60 * 10);
101         final boolean runInSeparateProcess = intent.getBooleanExtra(EXTRA_RUN_IN_SEPARATE_PROCESS,
102                 true);
103         mTestName = intent.getStringExtra(EXTRA_TEST_NAME) != null
104                 ? intent.getStringExtra(EXTRA_TEST_NAME) : "no-name";
105 
106         mCoordinator = new CrashTestCoordinator(getApplicationContext());
107 
108         String acceleratorName = intent.getStringExtra(EXTRA_ACCELERATOR_NAME);
109         boolean ignoreUnsupportedModels = intent.getBooleanExtra(EXTRA_IGNORE_UNSUPPORTED_MODELS,
110                 false);
111         boolean mmapModel = intent.getBooleanExtra(EXTRA_MEMORY_MAP_MODEL, false);
112 
113         final boolean runModelCompilationOnly = intent.getBooleanExtra(
114                 EXTRA_RUN_MODEL_COMPILATION_ONLY, false);
115 
116         mCoordinator.startTest(RunModelsInParallel.class,
117             RunModelsInParallel.intentInitializer(testList, threadCount,
118                 Duration.ofMillis(testDurationMillis), mTestName, acceleratorName,
119                 ignoreUnsupportedModels, runModelCompilationOnly, mmapModel),
120             mTestStatus, runInSeparateProcess, mTestName);
121 
122         mStopTestButton.setEnabled(true);
123     }
124 
endTests()125     private void endTests() {
126         mCoordinator.shutdown();
127         mCoordinator = null;
128     }
129 
130     // This method blocks until the tests complete and returns true if all tests completed
131     // successfully
132     @SuppressLint("DefaultLocale")
testResult()133     public CrashTestStatus.TestResult testResult() {
134         try {
135             final Intent intent = getIntent();
136             final long testDurationMillis = intent.getLongExtra(EXTRA_TEST_DURATION_MILLIS,
137                     60 * 10);
138             // Giving the test a bit of time to wrap up
139             final long testResultTimeout = testDurationMillis + SHUTDOWN_TIMEOUT;
140             boolean completed = mTestStatus.waitForCompletion(testResultTimeout, MILLISECONDS);
141             if (!completed) {
142                 showMessage(String.format(
143                         "Ending test '%s' since test result collection timeout of %d "
144                                 + "millis is expired",
145                         mTestName, testResultTimeout));
146                 endTests();
147             }
148         } catch (InterruptedException ignored) {
149             Thread.currentThread().interrupt();
150         }
151 
152         // If no result is available, assuming HANG
153         mTestStatus.compareAndSetResult(null, HANG);
154         return mTestStatus.result();
155     }
156 
onStopTestClicked(View view)157     public void onStopTestClicked(View view) {
158         showMessage("Stopping tests");
159         endTests();
160     }
161 
162     /**
163      * Kills the process running the tests.
164      *
165      * @throws IllegalStateException if the method is called for an in-process test.
166      * @throws RemoteException       if the test service is not reachable
167      */
killTestProcess()168     public void killTestProcess() throws RemoteException {
169         final Intent intent = getIntent();
170 
171         final boolean runInSeparateProcess = intent.getBooleanExtra(EXTRA_RUN_IN_SEPARATE_PROCESS,
172                 true);
173 
174         if (!runInSeparateProcess) {
175             throw new IllegalStateException("Cannot kill the test process in an in-process test!");
176         }
177 
178         Log.i(TAG, "Asking coordinator to kill test process");
179         mCoordinator.killCrashTestService();
180     }
181 }
182