blob: be1fffd0c4d21a605d2ca06777aa1c8c509f146d [file] [log] [blame]
/*
* Copyright 2020 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 androidx.benchmark.macro
import android.app.Instrumentation
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
const val TAG = "MacroBenchmarks"
// SELinux enforcement
private const val PERMISSIVE = "Permissive"
private const val ENFORCING = "Enforcing"
// Compile modes
const val SPEED = "speed"
const val SPEED_PROFILE = "speed-profile"
const val QUICKEN = "quicken"
const val VERIFY = "verify"
// All modes
private val COMPILE_MODES = listOf(SPEED, SPEED_PROFILE, QUICKEN, VERIFY)
// SELinux Permission Lock
private val permissiveLock = ReentrantReadWriteLock()
/**
* Drops the kernel page cache
*
*@param instrumentation The [Instrumentation] context.
*/
internal fun dropCaches(instrumentation: Instrumentation) {
val outputDirectory = instrumentation.context.cacheDir
val script = File.createTempFile("drop_cache_script", ".sh", outputDirectory)
script.setWritable(true)
script.setExecutable(/* executable */true, /* owner only */false)
val command = "echo 3 > /proc/sys/vm/drop_caches && echo Success || echo Failure"
script.writeText(command)
val device = instrumentation.device()
val result = device.executeShellCommand(script.toString())
Log.d(TAG, "drop caches output was $result")
}
/**
* Compiles the application with the specified filter.
* For more information: https://source.android.com/devices/tech/dalvik/jit-compiler
*/
internal fun compilationFilter(
instrumentation: Instrumentation,
packageName: String,
mode: String,
profileSaveTimeout: Long = 5000
) {
check(mode in COMPILE_MODES) {
"Invalid compilation mode. Must be one of ${COMPILE_MODES.joinToString(",")}"
}
val device = instrumentation.device()
if (mode == SPEED_PROFILE) {
// For speed profile compilation, ART team recommended to wait for 5 secs when app
// is in the foreground, dump the profile, wait for another 5 secs before
// speed-profile compilation.
Thread.sleep(profileSaveTimeout)
val response = device.executeShellCommand("killall -s SIGUSR1 $packageName")
if (response.isNotBlank()) {
Log.d(TAG, "Received dump profile response $response")
throw RuntimeException("Failed to dump profile for $packageName ($response)")
}
Thread.sleep(profileSaveTimeout)
}
Log.d(TAG, "Compiling $packageName ($mode)")
val response = device.executeShellCommand("cmd package compile -f -m $mode $packageName")
if (!response.contains("Success")) {
Log.d(TAG, "Received compile cmd response: $response")
throw RuntimeException("Failed to compile $packageName ($response)")
}
}
/**
* Clears existing compilation profiles.
*/
internal fun clearProfile(
instrumentation: Instrumentation,
packageName: String,
) {
Log.d(TAG, "Clearing profiles for $packageName")
instrumentation.device().executeShellCommand("cmd package compile --reset $packageName")
}
/**
* Presses the home button.
*/
fun pressHome(instrumentation: Instrumentation, delayDurationMs: Long = 300) {
instrumentation.device().pressHome()
// Sleep for statsd to update the metrics.
Thread.sleep(delayDurationMs)
}
/**
* Temporary, root-only hack to enable getting startup metrics
*/
fun withPermissiveSeLinuxPolicy(block: () -> Unit) {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val previousPolicy = getSeLinuxPolicyEnforced(instrumentation)
permissiveLock.read {
try {
if (previousPolicy == 1) {
setSeLinuxPolicyEnforced(instrumentation, 0)
}
block()
} finally {
if (previousPolicy == 1 && permissiveLock.readHoldCount == 1) {
setSeLinuxPolicyEnforced(instrumentation, previousPolicy)
}
}
}
}
/**
* Gets the SE Linux policy.
*/
private fun getSeLinuxPolicyEnforced(instrumentation: Instrumentation): Int {
// command returns "Permissive\n" or "Enforcing\n" (note the newline!)
val enforcingStatus = instrumentation.device().executeShellCommand("getenforce")
return if (enforcingStatus.contains(PERMISSIVE)) 0 else 1
}
/**
* Overrides the SE Linux policy.
*/
private fun setSeLinuxPolicyEnforced(instrumentation: Instrumentation, policy: Int) {
check(policy == 0 || policy == 1) {
"Policy can only be one of `0` or `1`"
}
// TODO: unable to see failure here, due to executeShellCommand output always being empty.
// for now, we just verify the `setenforce` worked with an extra `getenforce`
instrumentation.device().executeShellCommand("setenforce $policy")
if (getSeLinuxPolicyEnforced(instrumentation) != policy) {
throw IllegalStateException("\"setenforce $policy\" didn't succeed, is ADB session rooted?")
}
}
internal fun Instrumentation.device(): UiDevice {
return UiDevice.getInstance(this)
}