blob: 199d3d726083b1ad57350734691d309413eee6e3 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nn.benchmark.app;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Trace;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import com.android.nn.benchmark.core.BenchmarkException;
import com.android.nn.benchmark.core.BenchmarkResult;
import com.android.nn.benchmark.core.TestModels;
import com.android.nn.benchmark.core.TestModels.TestModelEntry;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* Benchmark test-case super-class.
*
* Helper code for managing NNAPI/NNAPI-less benchamarks.
*/
@RunWith(Parameterized.class)
public class BenchmarkTestBase extends ActivityInstrumentationTestCase2<NNBenchmark> {
// Only run 1 iteration now to fit the MediumTest time requirement.
// One iteration means running the tests continuous for 1s.
private NNBenchmark mActivity;
protected final TestModelEntry mModel;
// The default 0.3s warmup and 1.0s runtime give reasonably repeatable results (run-to-run
// variability of ~20%) when run under performance settings (fixed CPU cores enabled and at
// fixed frequency). The continuous build is not allowed to take much more than 1s so we
// can't change the defaults for @MediumTest.
protected static final float WARMUP_SHORT_SECONDS = 0.3f;
protected static final float RUNTIME_SHORT_SECONDS = 1.f;
// For running like a normal user-initiated app, the variability for 0.3s/1.0s is easily 3x.
// With 2s/10s it's 20-50%. This @LargeTest allows running with these timings.
protected static final float WARMUP_REPEATABLE_SECONDS = 2.f;
protected static final float RUNTIME_REPEATABLE_SECONDS = 10.f;
// For running a complete dataset, the run should complete under 5 minutes.
protected static final float COMPLETE_SET_TIMEOUT_SECOND = 300.f;
// For running compilation benchmarks.
protected static final float COMPILATION_WARMUP_SECONDS = 2.f;
protected static final float COMPILATION_RUNTIME_SECONDS = 10.f;
protected static final int COMPILATION_MAX_ITERATIONS = 100;
public BenchmarkTestBase(TestModelEntry model) {
super(NNBenchmark.class);
mModel = model;
}
protected void setUseNNApi(boolean useNNApi) {
mActivity.setUseNNApi(useNNApi);
}
protected void setCompleteInputSet(boolean completeInputSet) {
mActivity.setCompleteInputSet(completeInputSet);
}
protected void enableCompilationCachingBenchmarks() {
mActivity.enableCompilationCachingBenchmarks(COMPILATION_WARMUP_SECONDS,
COMPILATION_RUNTIME_SECONDS, COMPILATION_MAX_ITERATIONS);
}
// Initialize the parameter for ImageProcessingActivityJB.
protected void prepareTest() {
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
mActivity = getActivity();
mActivity.prepareInstrumentationTest();
setUseNNApi(true);
}
public void waitUntilCharged() {
BenchmarkTestBase.waitUntilCharged(mActivity, -1);
}
public static void waitUntilCharged(Context context, int minChargeLevel) {
Log.v(NNBenchmark.TAG, "Waiting for the device to charge");
final CountDownLatch chargedLatch = new CountDownLatch(1);
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
int percentage = level * 100 / scale;
if (minChargeLevel > 0 && minChargeLevel < 100) {
if (percentage >= minChargeLevel) {
Log.v(NNBenchmark.TAG,
String.format(
"Battery level: %d%% is greater than requested %d%%. "
+ "Considering the device ready.",
percentage, minChargeLevel));
chargedLatch.countDown();
return;
}
}
Log.v(NNBenchmark.TAG, String.format("Battery level: %d%%", percentage));
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
if (status == BatteryManager.BATTERY_STATUS_FULL) {
chargedLatch.countDown();
} else if (status != BatteryManager.BATTERY_STATUS_CHARGING) {
Log.e(NNBenchmark.TAG,
String.format("Device is not charging, status is %d", status));
}
}
};
context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
try {
chargedLatch.await();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
context.unregisterReceiver(receiver);
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
prepareTest();
setActivityInitialTouchMode(false);
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
}
interface Joinable extends Runnable {
// Syncrhonises the caller with the completion of the current action
void join();
}
class TestAction implements Joinable {
private final TestModelEntry mTestModel;
private final float mMaxWarmupTimeSeconds;
private final float mMaxRunTimeSeconds;
private final CountDownLatch actionComplete;
BenchmarkResult mResult;
Throwable mException;
public TestAction(TestModelEntry testName, float maxWarmupTimeSeconds,
float maxRunTimeSeconds) {
mTestModel = testName;
mMaxWarmupTimeSeconds = maxWarmupTimeSeconds;
mMaxRunTimeSeconds = maxRunTimeSeconds;
actionComplete = new CountDownLatch(1);
}
public void run() {
Log.v(NNBenchmark.TAG, String.format(
"Starting benchmark for test '%s' running for max %f seconds",
mTestModel.mTestName,
mMaxRunTimeSeconds));
try {
mResult = mActivity.runSynchronously(
mTestModel, mMaxWarmupTimeSeconds, mMaxRunTimeSeconds);
} catch (BenchmarkException | IOException e) {
mException = e;
Log.e(NNBenchmark.TAG,
String.format("Error running Benchmark for test '%s'", mTestModel), e);
} catch (Throwable e) {
mException = e;
Log.e(NNBenchmark.TAG,
String.format("Failure running Benchmark for test '%s'!!", mTestModel), e);
throw e;
} finally {
actionComplete.countDown();
}
}
public BenchmarkResult getBenchmark() {
if (mException != null) {
throw new Error("run failed", mException);
}
return mResult;
}
@Override
public void join() {
try {
actionComplete.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.v(NNBenchmark.TAG, "Interrupted while waiting for action running", e);
}
}
}
// Set the benchmark thread to run on ui thread
// Synchronized the thread such that the test will wait for the benchmark thread to finish
public void runOnUiThread(Joinable action) {
mActivity.runOnUiThread(action);
action.join();
}
public void runTest(TestAction ta, String testName) {
float sum = 0;
// For NNAPI systrace usage documentation, see
// frameworks/ml/nn/common/include/Tracing.h.
final String traceName = "[NN_LA_PO]" + testName;
try {
Trace.beginSection(traceName);
runOnUiThread(ta);
} finally {
Trace.endSection();
}
BenchmarkResult bmValue = ta.getBenchmark();
// post result to INSTRUMENTATION_STATUS
getInstrumentation().sendStatus(Activity.RESULT_OK, bmValue.toBundle(testName));
}
@Parameters(name = "{0}")
public static List<TestModelEntry> modelsList() {
return TestModels.modelsList();
}
}