Skip to content

Commit 8acbeb3

Browse files
Adds Benchmark module to evaluate the SDK's first run performance (#1489)
* Operator.evaluateLibrary function Generalization of FhirEngineRetrieveProvider * Fixes from spotlessApply * Moving COVID tests to its own directory. * Removing redundant (contextPath != "id") * Using Truth library and clearing up the logs * BugFix on assetPath and Organization ID * New Benchmark Module * Simplifying tests. * Deleting covid tests * Requiring open to be not null * Updating CQL Evaluation tests * adding a profilable tag * Fixing test for the new benchmark * Renaming tests. * Enhancing the test coverage * Sorting dependencies alphabetically * Spotless apply * Adding CQL Parser test * Typo * Migrating Benchmark Plugin to 1.1.0 Removing unnecessary dependencies on Room Better describing the role of execution of the tests * Spotless Apply * Following the design of the new PR for java versions * Spotless apply * Using workflow's minSDK * multidex is not needed. * Using workflow-testing * Upgrading to CQL Combo 2.0 classes. * Spotless Apply * order imports alphabetically * Don't change plugin version (potentially a merge issue) * Use just one runWithTimingDisabled. * Adding instructions in how to run the benchmark test
1 parent 5f8790d commit 8acbeb3

16 files changed

+678
-0
lines changed

benchmark/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Android FHIR SDK Benchmark module
2+
3+
Contains test cases that evaluate the performance of individual tasks executed for the first time directly on hardware.
4+
5+
The test cases are designed to run in sequence of their alphabetic order to make sure larger tasks do not build cache for smaller ones. Their class names are prefixed by an extra letter to inform their position relative to others in the list.
6+
7+
# How to run the benchmark
8+
9+
In Android Studio, set your build variants to `release` and run your benchmark as you would any `@Test` using the gutter action next to your test class or method.
10+
11+
![gutter test action](https://developer.android.com/static/topic/performance/images/benchmark_images/microbenchmark_run.png)
12+
13+
The results will be similar to this:
14+
```
15+
48,093,078 ns trace A_JacksonMapperBenchmark.loadJsonMapper
16+
2,261,548,715 ns trace B_FhirContextLoaderBenchmark.loadR4
17+
2,928,293,365 ns trace B_FhirContextLoaderBenchmark.loadR5
18+
337,669,721 ns trace B_FhirContextLoaderBenchmark.loadDstu2
19+
1,744,938,507 ns trace B_FhirContextLoaderBenchmark.loadDstu3
20+
6,817,953,752 ns trace C_CqlEngineFhirContextLoaderBenchmark.loadDstu2FhirModelResolver
21+
3,704,489,380 ns trace C_CqlEngineFhirContextLoaderBenchmark.loadR4FhirModelResolver
22+
2,814,451,999 ns trace C_CqlEngineFhirContextLoaderBenchmark.loadDstu3FhirModelResolver
23+
675,991,839 ns trace D_FhirJsonParserBenchmark.parseLightFhirLibrary
24+
1,238,212,883 ns trace D_FhirJsonParserBenchmark.parseLightFhirBundle
25+
2,785,964,288 ns trace E_ElmJsonLibraryLoaderBenchmark.parseImmunityCheckCqlFromFhirLibrary
26+
713,779,915 ns trace E_ElmJsonLibraryLoaderBenchmark.parseFhirHelpersCqlFromFhirLibrary
27+
121,204,232 ns trace F_EngineDatabaseBenchmark.createAndGet
28+
9,833,892,387 ns trace G_CqlEvaluatorBenchmark.evaluatesLibrary
29+
```
30+
31+
Alternatively, from the command line, run the connectedCheck to run all of the tests from specified Gradle module:
32+
33+
```bash
34+
./gradlew benchmark:connectedReleaseAndroidTest
35+
```
36+
37+
In this case, results will be saved to the `outputs/androidTest-results/connected/<device>/test-result.pb`. To visualize on Android Studio, click Run / Import Tests From File and find the `.pb` file
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
22+
23+
-dontobfuscate
24+
25+
-ignorewarnings
26+
27+
-keepattributes *Annotation*
28+
29+
-dontnote junit.framework.**
30+
-dontnote junit.runner.**
31+
32+
-dontwarn androidx.test.**
33+
-dontwarn org.junit.**
34+
-dontwarn org.hamcrest.**
35+
-dontwarn com.squareup.javawriter.JavaWriter
36+
37+
-keepclasseswithmembers @org.junit.runner.RunWith public class *

benchmark/build.gradle.kts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
plugins {
2+
id(Plugins.BuildPlugins.androidLib)
3+
id(Plugins.BuildPlugins.kotlinAndroid)
4+
id(Plugins.BuildPlugins.kotlinKapt)
5+
id(Plugins.BuildPlugins.benchmark)
6+
id(Plugins.BuildPlugins.jetbrainsKotlinAndroid)
7+
}
8+
9+
android {
10+
compileSdk = Sdk.compileSdk
11+
12+
compileOptions {
13+
sourceCompatibility = Java.sourceCompatibility
14+
targetCompatibility = Java.targetCompatibility
15+
}
16+
17+
kotlinOptions { jvmTarget = Java.kotlinJvmTarget.toString() }
18+
19+
defaultConfig {
20+
minSdk = Sdk.minSdkWorkflow
21+
targetSdk = Sdk.targetSdk
22+
23+
testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
24+
// Runs only once
25+
testInstrumentationRunnerArguments["androidx.benchmark.dryRunMode.enable"] = "true"
26+
// Includes Startup time
27+
testInstrumentationRunnerArguments["androidx.benchmark.startupMode.enable"] = "true"
28+
}
29+
30+
testBuildType = "release"
31+
buildTypes {
32+
debug {
33+
// Since isDebuggable can't be modified by gradle for library modules,
34+
// it must be done in a manifest - see src/androidTest/AndroidManifest.xml
35+
isMinifyEnabled = true
36+
proguardFiles(
37+
getDefaultProguardFile("proguard-android-optimize.txt"),
38+
"benchmark-proguard-rules.pro"
39+
)
40+
}
41+
}
42+
packagingOptions {
43+
resources.excludes.addAll(
44+
listOf(
45+
"license.html",
46+
"META-INF/ASL2.0",
47+
"META-INF/ASL-2.0.txt",
48+
"META-INF/DEPENDENCIES",
49+
"META-INF/LGPL-3.0.txt",
50+
"META-INF/LICENSE",
51+
"META-INF/LICENSE.txt",
52+
"META-INF/license.txt",
53+
"META-INF/license.html",
54+
"META-INF/LICENSE.md",
55+
"META-INF/NOTICE",
56+
"META-INF/NOTICE.txt",
57+
"META-INF/NOTICE.md",
58+
"META-INF/notice.txt",
59+
"META-INF/LGPL-3.0.txt",
60+
"META-INF/sun-jaxb.episode",
61+
"META-INF/*.kotlin_module",
62+
"readme.html",
63+
)
64+
)
65+
}
66+
}
67+
68+
configurations {
69+
all {
70+
exclude(module = "xpp3")
71+
exclude(module = "xpp3_min")
72+
exclude(module = "xmlpull")
73+
exclude(module = "javax.json")
74+
exclude(module = "jcl-over-slf4j")
75+
exclude(group = "org.apache.httpcomponents")
76+
// Remove this after this issue has been fixed:
77+
// https://github.com/cqframework/clinical_quality_language/issues/799
78+
exclude(module = "antlr4")
79+
}
80+
}
81+
82+
dependencies {
83+
androidTestImplementation(Dependencies.AndroidxTest.benchmarkJunit)
84+
androidTestImplementation(Dependencies.AndroidxTest.extJunit)
85+
androidTestImplementation(Dependencies.AndroidxTest.runner)
86+
androidTestImplementation(Dependencies.Cql.engineJackson)
87+
androidTestImplementation(Dependencies.Cql.evaluator)
88+
androidTestImplementation(Dependencies.Cql.evaluatorBuilder)
89+
androidTestImplementation(Dependencies.junit)
90+
androidTestImplementation(Dependencies.Kotlin.kotlinCoroutinesAndroid)
91+
androidTestImplementation(Dependencies.truth)
92+
93+
androidTestImplementation(project(":engine"))
94+
androidTestImplementation(project(":workflow"))
95+
androidTestImplementation(project(":workflow-testing"))
96+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<manifest
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
package="com.google.android.fhir.benchmark"
6+
>
7+
8+
<!--
9+
Important: disable debugging for accurate performance results
10+
11+
In a com.android.library project, this flag must be disabled from this
12+
manifest, as it is not possible to override this flag from Gradle.
13+
-->
14+
<application
15+
android:debuggable="false"
16+
tools:ignore="HardcodedDebugMode"
17+
tools:replace="android:debuggable"
18+
>
19+
<profileable android:shell="true" />
20+
</application>
21+
</manifest>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2022 Google LLC
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.google.android.fhir.benchmark
18+
19+
import androidx.benchmark.junit4.BenchmarkRule
20+
import androidx.benchmark.junit4.measureRepeated
21+
import androidx.test.ext.junit.runners.AndroidJUnit4
22+
import com.google.common.truth.Truth.assertThat
23+
import org.junit.Rule
24+
import org.junit.Test
25+
import org.junit.runner.RunWith
26+
import org.opencds.cqf.cql.engine.serializing.jackson.JsonCqlMapper
27+
28+
@RunWith(AndroidJUnit4::class)
29+
class A_JacksonMapperBenchmark {
30+
@get:Rule val benchmarkRule = BenchmarkRule()
31+
32+
/**
33+
* The JSONMapper and the XMLMapper take 800ms to initialize the first time on Desktop. They seem
34+
* to take less time on mobile or there is something pre-loading this object. Either way, it's
35+
* important to keep an eye on it.
36+
*/
37+
@Test
38+
fun loadJsonMapper() {
39+
benchmarkRule.measureRepeated { assertThat(JsonCqlMapper.getMapper()).isNotNull() }
40+
}
41+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2022 Google LLC
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.google.android.fhir.benchmark
18+
19+
import androidx.benchmark.junit4.BenchmarkRule
20+
import androidx.benchmark.junit4.measureRepeated
21+
import androidx.test.ext.junit.runners.AndroidJUnit4
22+
import ca.uhn.fhir.context.FhirContext
23+
import com.google.common.truth.Truth.assertThat
24+
import org.hl7.fhir.r4.model.Enumerations
25+
import org.junit.Rule
26+
import org.junit.Test
27+
import org.junit.runner.RunWith
28+
29+
@RunWith(AndroidJUnit4::class)
30+
class B_FhirContextLoaderBenchmark {
31+
32+
@get:Rule val benchmarkRule = BenchmarkRule()
33+
34+
/**
35+
* FhirContexts generally take 2 seconds to load completely. This test mimics that loading time.
36+
* getResourceDefinition forces the initialization of the Context.
37+
*/
38+
@Test
39+
fun loadDstu2() {
40+
benchmarkRule.measureRepeated {
41+
assertThat(
42+
FhirContext.forDstu2().getResourceDefinition(Enumerations.ResourceType.ACCOUNT.toCode())
43+
)
44+
.isNotNull()
45+
}
46+
}
47+
48+
@Test
49+
fun loadDstu3() {
50+
benchmarkRule.measureRepeated {
51+
assertThat(
52+
FhirContext.forDstu3().getResourceDefinition(Enumerations.ResourceType.ACCOUNT.toCode())
53+
)
54+
.isNotNull()
55+
}
56+
}
57+
58+
@Test
59+
fun loadR4() {
60+
benchmarkRule.measureRepeated {
61+
assertThat(
62+
FhirContext.forR4().getResourceDefinition(Enumerations.ResourceType.ACCOUNT.toCode())
63+
)
64+
.isNotNull()
65+
}
66+
}
67+
68+
@Test
69+
fun loadR5() {
70+
benchmarkRule.measureRepeated {
71+
assertThat(
72+
FhirContext.forR5().getResourceDefinition(Enumerations.ResourceType.ACCOUNT.toCode())
73+
)
74+
.isNotNull()
75+
}
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2022 Google LLC
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.google.android.fhir.benchmark
18+
19+
import androidx.benchmark.junit4.BenchmarkRule
20+
import androidx.benchmark.junit4.measureRepeated
21+
import androidx.test.ext.junit.runners.AndroidJUnit4
22+
import com.google.common.truth.Truth.assertThat
23+
import org.junit.Rule
24+
import org.junit.Test
25+
import org.junit.runner.RunWith
26+
import org.opencds.cqf.cql.engine.fhir.model.Dstu2FhirModelResolver
27+
import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver
28+
import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver
29+
30+
@RunWith(AndroidJUnit4::class)
31+
class C_CqlEngineFhirContextLoaderBenchmark {
32+
33+
@get:Rule val benchmarkRule = BenchmarkRule()
34+
35+
/**
36+
* The CQL engine FhirModelResolvers need a complete FhirContext loaded with added classes. The
37+
* loading of the FhirContext has already happen on B, thus this is just for the added classes
38+
* from the Engine.
39+
*/
40+
@Test
41+
fun loadDstu2FhirModelResolver() {
42+
benchmarkRule.measureRepeated { assertThat(Dstu2FhirModelResolver()).isNotNull() }
43+
}
44+
45+
@Test
46+
fun loadDstu3FhirModelResolver() {
47+
benchmarkRule.measureRepeated { assertThat(Dstu3FhirModelResolver()).isNotNull() }
48+
}
49+
50+
@Test
51+
fun loadR4FhirModelResolver() {
52+
benchmarkRule.measureRepeated { assertThat(R4FhirModelResolver()).isNotNull() }
53+
}
54+
}

0 commit comments

Comments
 (0)