Add WorkManager Benchmarks.
* The first benchmark measures the cost to create an instance of WorkManager with no outstanding work left.
* The second benchmark measures the cost to instantitate WorkManager with pending work which needs to be executed.
Test: Added benchmarks.
Fixes: b/126406798
Change-Id: Iab86a1ee2a1a72ac7c5f7f3b7233713feb087701
diff --git a/settings.gradle b/settings.gradle
index 8e9171e..5dcbc7b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -233,6 +233,7 @@
includeProject(":work:work-runtime-ktx", "work/workmanager-ktx")
includeProject(":work:work-rxjava2", "work/workmanager-rxjava2")
includeProject(":work:work-testing", "work/workmanager-testing")
+includeProject(":work:work-benchmark", "work/workmanager-benchmark")
includeProject(":work:integration-tests:testapp", "work/integration-tests/testapp")
/////////////////////////////
diff --git a/work/workmanager-benchmark/build.gradle b/work/workmanager-benchmark/build.gradle
new file mode 100644
index 0000000..e64d2a7
--- /dev/null
+++ b/work/workmanager-benchmark/build.gradle
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+
+import androidx.build.AndroidXExtension
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+ id("androidx.benchmark")
+}
+
+dependencies {
+ androidTestImplementation(project(':work:work-runtime-ktx'))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
+ androidTestImplementation(WORK_ARCH_ROOM_RUNTIME)
+ androidTestImplementation(JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
+ androidTestImplementation(KOTLIN_STDLIB)
+ androidTestImplementation(KOTLIN_COROUTINES)
+}
+
+androidx {
+ name = "Android WorkManager Benchmarks"
+ publish = Publish.NONE
+ mavenVersion = LibraryVersions.WORK
+ mavenGroup = LibraryGroups.WORK
+ inceptionYear = "2019"
+ description = "Android WorkManager Benchmark Library"
+ url = AndroidXExtension.ARCHITECTURE_URL
+}
diff --git a/work/workmanager-benchmark/src/androidTest/AndroidManifest.xml b/work/workmanager-benchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..9ad6fef
--- /dev/null
+++ b/work/workmanager-benchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.work.benchmark.test">
+
+ <!-- Important: disable debuggable for accurate performance results -->
+ <application
+ android:debuggable="false"
+ tools:replace="android:debuggable">
+ </application>
+</manifest>
diff --git a/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/DispatchingExecutor.kt b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/DispatchingExecutor.kt
new file mode 100644
index 0000000..5b6b635
--- /dev/null
+++ b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/DispatchingExecutor.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 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.work.benchmark
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import java.util.concurrent.Executor
+
+/**
+ * An [Executor] where we can await termination of all commands.
+ */
+class DispatchingExecutor : Executor {
+ val job = Job()
+ private val scope = CoroutineScope(Dispatchers.Default + job)
+ override fun execute(command: Runnable) {
+ scope.launch {
+ command.run()
+ }
+ }
+}
diff --git a/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/InitializeBenchmark.kt b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/InitializeBenchmark.kt
new file mode 100644
index 0000000..3d33749
--- /dev/null
+++ b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/InitializeBenchmark.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2019 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.work.benchmark
+
+import android.content.Context
+import android.util.Log
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.work.Configuration
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.impl.WorkDatabase
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.utils.SerialExecutor
+import androidx.work.impl.utils.taskexecutor.TaskExecutor
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.Executor
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class InitializeBenchmark {
+
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+ private lateinit var context: Context
+ private lateinit var executor: DispatchingExecutor
+ private lateinit var taskExecutor: TaskExecutor
+ private lateinit var configuration: Configuration
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+
+ // Use a DispatchingExecutor to avoid having to wait for all the tasks to be done
+ // in the actual benchmark.
+ executor = DispatchingExecutor()
+ val serialExecutor = SerialExecutor(executor)
+
+ taskExecutor = object : TaskExecutor {
+ override fun postToMainThread(runnable: Runnable) {
+ serialExecutor.execute(runnable)
+ }
+
+ override fun getMainThreadExecutor(): Executor {
+ return serialExecutor
+ }
+
+ override fun executeOnBackgroundThread(runnable: Runnable) {
+ serialExecutor.execute(runnable)
+ }
+
+ override fun getBackgroundExecutor(): SerialExecutor {
+ return serialExecutor
+ }
+ }
+
+ configuration = Configuration.Builder()
+ .setTaskExecutor(executor)
+ .setExecutor(executor)
+ .setMinimumLoggingLevel(Log.DEBUG)
+ .build()
+ }
+
+ @Test
+ fun initializeSimple() {
+ benchmarkRule.measureRepeated {
+ // Runs ForceStopRunnable
+ val database = WorkDatabase.create(context, configuration.taskExecutor, false)
+ WorkManagerImpl(context, configuration, taskExecutor, database)
+ runWithTimingDisabled {
+ runBlocking {
+ executor.job.children.forEach {
+ it.join()
+ }
+ }
+ database.close()
+ }
+ }
+ }
+
+ @Test
+ fun initializeWithWorkLeft() {
+ val count = 20
+ benchmarkRule.measureRepeated {
+ val database = WorkDatabase.create(context, configuration.taskExecutor, false)
+ runWithTimingDisabled {
+ for (i in 0 until count) {
+ val request = OneTimeWorkRequestBuilder<NoOpWorker>()
+ val state =
+ if (i <= count - 2) WorkInfo.State.SUCCEEDED else WorkInfo.State.RUNNING
+ request.setInitialState(state)
+ database.workSpecDao().insertWorkSpec(request.build().workSpec)
+ }
+ }
+ // Runs ForceStopRunnable
+ WorkManagerImpl(context, configuration, taskExecutor, database)
+ // Prune records for the next run.
+ runWithTimingDisabled {
+ runBlocking {
+ executor.job.children.forEach {
+ it.join()
+ }
+ }
+ database.workSpecDao().pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast()
+ database.close()
+ }
+ }
+ }
+}
diff --git a/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/NoOpWorker.kt b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/NoOpWorker.kt
new file mode 100644
index 0000000..760ffd9
--- /dev/null
+++ b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/NoOpWorker.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 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.work.benchmark
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+
+/**
+ * Does not do anything useful except returning a [androidx.work.ListenableWorker.Result.success].
+ */
+class NoOpWorker(context: Context, parameters: WorkerParameters) :
+ CoroutineWorker(context, parameters) {
+ override suspend fun doWork(): Result {
+ return Result.success()
+ }
+}
\ No newline at end of file
diff --git a/work/workmanager-benchmark/src/main/AndroidManifest.xml b/work/workmanager-benchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4eb2ff3
--- /dev/null
+++ b/work/workmanager-benchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+<manifest package="androidx.work.benchmark" />
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index e5120d2..9c52835b 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -93,7 +93,7 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static void setDelegate(WorkManagerImpl delegate) {
+ public static void setDelegate(@Nullable WorkManagerImpl delegate) {
synchronized (sLock) {
sDelegatedInstance = delegate;
}
@@ -220,10 +220,33 @@
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
boolean useTestDatabase) {
+ this(context,
+ configuration,
+ workTaskExecutor,
+ WorkDatabase.create(
+ context.getApplicationContext(),
+ configuration.getTaskExecutor(),
+ useTestDatabase)
+ );
+ }
+ /**
+ * Create an instance of {@link WorkManagerImpl}.
+ *
+ * @param context The application {@link Context}
+ * @param configuration The {@link Configuration} configuration
+ * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
+ * enqueueing, scheduling, cancellation, etc.
+ * @param database The {@link WorkDatabase}
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public WorkManagerImpl(
+ @NonNull Context context,
+ @NonNull Configuration configuration,
+ @NonNull TaskExecutor workTaskExecutor,
+ @NonNull WorkDatabase database) {
Context applicationContext = context.getApplicationContext();
- WorkDatabase database = WorkDatabase.create(
- applicationContext, configuration.getTaskExecutor(), useTestDatabase);
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
List<Scheduler> schedulers = createSchedulers(applicationContext, workTaskExecutor);
Processor processor = new Processor(