-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Check for code usage fluctuations in native images
This aims to aid in detecting code usage fluctuations as early as possible. I set the threshold to 3% which might be a bit tight. Ideally most (if not all) ITs being tested in native mode should include such checks. This way when a dependency gets updated and brings in more bloat, or a GraalVM change results in more code becoming reachable we will be able to notice. If significant fluctuations between GraalVM/Mandrel versions are detected the annotations `@DisableIfBuiltWithGraalVMOlderThan` and `@DisableIfBuiltWithGraalVMNewerThan` may be used to run tests with different properties files. The properties files can be generated by running the following script, passing the build output json file as the first parameter and the path to the target properties file as the second parameter (it will be overwritten), after a failed run. This way enabling the check for a new integration test is a matter of: 1. copying `ImageMetricsITCase.java` in the corresponding folder 2. running the test once 3. running the following script passing the build output json file as the first parameter. ```bash KEYS=( \ image_details.total_bytes \ analysis_results.types.reachable \ analysis_results.methods.reachable \ analysis_results.fields.reachable \ analysis_results.types.reflection \ analysis_results.methods.reflection \ analysis_results.fields.reflection \ analysis_results.types.jni \ analysis_results.methods.jni \ analysis_results.fields.jni \ ) echo "# Properties file used by ImageMetricsITCase" > $2 for i in $KEYS do echo "$i=$(jq .$i $1) >> $2 echo "$i.tolerance=3 >> $2 done ```
- Loading branch information
Showing
7 changed files
with
214 additions
and
0 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
...jpa-postgresql-withxml/src/test/java/io/quarkus/it/jpa/postgresql/ImageMetricsITCase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.quarkus.it.jpa.postgresql; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import io.quarkus.test.junit.QuarkusIntegrationTest; | ||
import io.quarkus.test.junit.nativeimage.BuildOutput; | ||
|
||
@QuarkusIntegrationTest | ||
public class ImageMetricsITCase { | ||
@Test | ||
public void verifyImageMetrics() { | ||
BuildOutput buildOutput = new BuildOutput(); | ||
buildOutput.verifyImageMetrics(); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
integration-tests/jpa-postgresql-withxml/src/test/resources/image-metrics-test.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
image_details.total_bytes=81172328 | ||
image_details.total_bytes.tolerance=3 | ||
analysis_results.types.reachable=18854 | ||
analysis_results.types.reachable.tolerance=3 | ||
analysis_results.methods.reachable=93385 | ||
analysis_results.methods.reachable.tolerance=3 | ||
analysis_results.fields.reachable=28156 | ||
analysis_results.fields.reachable.tolerance=3 | ||
analysis_results.types.reflection=5978 | ||
analysis_results.types.reflection.tolerance=3 | ||
analysis_results.methods.reflection=4107 | ||
analysis_results.methods.reflection.tolerance=3 | ||
analysis_results.fields.reflection=127 | ||
analysis_results.fields.reflection.tolerance=3 | ||
analysis_results.types.jni=63 | ||
analysis_results.types.jni.tolerance=1 | ||
analysis_results.methods.jni=55 | ||
analysis_results.methods.jni.tolerance=1 | ||
analysis_results.fields.jni=68 | ||
analysis_results.fields.jni.tolerance=1 |
15 changes: 15 additions & 0 deletions
15
...n-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/ImageMetricsITCase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.quarkus.it.jpa.postgresql; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import io.quarkus.test.junit.QuarkusIntegrationTest; | ||
import io.quarkus.test.junit.nativeimage.BuildOutput; | ||
|
||
@QuarkusIntegrationTest | ||
public class ImageMetricsITCase { | ||
@Test | ||
public void verifyImageMetrics() { | ||
BuildOutput buildOutput = new BuildOutput(); | ||
buildOutput.verifyImageMetrics(); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
integration-tests/jpa-postgresql/src/test/resources/image-metrics-test.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
image_details.total_bytes=72185704 | ||
image_details.total_bytes.tolerance=3 | ||
analysis_results.types.reachable=17905 | ||
analysis_results.types.reachable.tolerance=3 | ||
analysis_results.methods.reachable=87856 | ||
analysis_results.methods.reachable.tolerance=3 | ||
analysis_results.fields.reachable=25370 | ||
analysis_results.fields.reachable.tolerance=3 | ||
analysis_results.types.reflection=5659 | ||
analysis_results.types.reflection.tolerance=3 | ||
analysis_results.methods.reflection=4200 | ||
analysis_results.methods.reflection.tolerance=3 | ||
analysis_results.fields.reflection=154 | ||
analysis_results.fields.reflection.tolerance=3 | ||
analysis_results.types.jni=63 | ||
analysis_results.types.jni.tolerance=1 | ||
analysis_results.methods.jni=55 | ||
analysis_results.methods.jni.tolerance=1 | ||
analysis_results.fields.jni=68 | ||
analysis_results.fields.jni.tolerance=1 |
15 changes: 15 additions & 0 deletions
15
integration-tests/main/src/test/java/io/quarkus/it/main/ImageMetricsITCase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.quarkus.it.main; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import io.quarkus.test.junit.QuarkusIntegrationTest; | ||
import io.quarkus.test.junit.nativeimage.BuildOutput; | ||
|
||
@QuarkusIntegrationTest | ||
public class ImageMetricsITCase { | ||
@Test | ||
public void verifyImageMetrics() { | ||
BuildOutput buildOutput = new BuildOutput(); | ||
buildOutput.verifyImageMetrics(); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
integration-tests/main/src/test/resources/image-metrics-test.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
image_details.total_bytes=138778008 | ||
image_details.total_bytes.tolerance=3 | ||
# TODO: Switch to using analysis_results.types.reachable key once we drop support for GraalVM 22.3.0 | ||
analysis_results.classes.reachable=30005 | ||
analysis_results.classes.reachable.tolerance=3 | ||
analysis_results.methods.reachable=149440 | ||
analysis_results.methods.reachable.tolerance=3 | ||
analysis_results.fields.reachable=44161 | ||
analysis_results.fields.reachable.tolerance=3 | ||
# TODO: Switch to using analysis_results.types.reflection key once we drop support for GraalVM 22.3.0 | ||
analysis_results.classes.reflection=8966 | ||
analysis_results.classes.reflection.tolerance=3 | ||
analysis_results.methods.reflection=7346 | ||
analysis_results.methods.reflection.tolerance=3 | ||
analysis_results.fields.reflection=438 | ||
analysis_results.fields.reflection.tolerance=3 | ||
# TODO: Switch to using analysis_results.types.jni key once we drop support for GraalVM 22.3.0 | ||
analysis_results.classes.jni=64 | ||
analysis_results.classes.jni.tolerance=1 | ||
analysis_results.methods.jni=55 | ||
analysis_results.methods.jni.tolerance=1 | ||
analysis_results.fields.jni=70 | ||
analysis_results.fields.jni.tolerance=1 |
106 changes: 106 additions & 0 deletions
106
test-framework/junit5/src/main/java/io/quarkus/test/junit/nativeimage/BuildOutput.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package io.quarkus.test.junit.nativeimage; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.Locale; | ||
import java.util.Properties; | ||
|
||
import jakarta.json.Json; | ||
import jakarta.json.JsonObject; | ||
|
||
import org.junit.jupiter.api.Assertions; | ||
|
||
/** | ||
* This is a general utility to assert via | ||
* unit testing how many classes, methods, objects etc. have been included in a native-image. | ||
* <p> | ||
* For detailed information and explanations on the build output, visit | ||
* <a href="https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md">the upstream GraalVM | ||
* documentation</a>. | ||
*/ | ||
public class BuildOutput { | ||
|
||
private static final String IMAGE_METRICS_TEST_PROPERTIES = "image-metrics-test.properties"; | ||
private final JsonObject buildOutput; | ||
|
||
public BuildOutput() { | ||
this.buildOutput = getBuildOutput(); | ||
} | ||
|
||
public void verifyImageMetrics() { | ||
verifyImageMetrics(IMAGE_METRICS_TEST_PROPERTIES); | ||
} | ||
|
||
public void verifyImageMetrics(String propertiesFileName) { | ||
Properties properties = getProperties(propertiesFileName); | ||
|
||
properties.forEach((key, value) -> { | ||
if (((String) key).endsWith(".tolerance")) { | ||
return; | ||
} | ||
String[] keyParts = ((String) key).split("\\."); | ||
String tolerance = properties.getProperty(key + ".tolerance"); | ||
assert tolerance != null : "tolerance not defined for " + key; | ||
assertValueWithinRange(Integer.parseInt((String) value), Integer.parseInt(tolerance), keyParts); | ||
}); | ||
} | ||
|
||
private Properties getProperties(String propertiesFileName) { | ||
Properties properties = new Properties(); | ||
try { | ||
properties.load(getClass().getClassLoader().getResourceAsStream(propertiesFileName)); | ||
} catch (IOException e) { | ||
Assertions.fail("Could not load properties from " + propertiesFileName, e); | ||
} | ||
return properties; | ||
} | ||
|
||
private void assertValueWithinRange(int expectedValue, int tolerancePercentage, String... key) { | ||
JsonObject currentObject = buildOutput; | ||
for (int i = 0; i < key.length - 1; i++) { | ||
currentObject = currentObject.getJsonObject(key[i]); | ||
} | ||
String lastKey = key[key.length - 1]; | ||
int actualValue = currentObject.getInt(lastKey); | ||
Assertions.assertTrue(isNumberWithinRange(expectedValue, actualValue, tolerancePercentage), | ||
"Expected " + String.join(".", key) + " to be within range [" + expectedValue + " +- " + tolerancePercentage | ||
+ "%] but was " + actualValue); | ||
} | ||
|
||
private boolean isNumberWithinRange(int expectedNumberOfClasses, int actualNumberOfClasses, int tolerancePercentage) { | ||
final int lowerBound = expectedNumberOfClasses - (expectedNumberOfClasses * tolerancePercentage / 100); | ||
final int upperBound = expectedNumberOfClasses + (expectedNumberOfClasses * tolerancePercentage / 100); | ||
return actualNumberOfClasses >= lowerBound && actualNumberOfClasses <= upperBound; | ||
} | ||
|
||
private static JsonObject getBuildOutput() { | ||
final Path buildOutputPath = getBuildOutputPath(); | ||
try (InputStream inputStream = Files.newInputStream(buildOutputPath)) { | ||
return Json.createReader(inputStream).readObject(); | ||
} catch (Exception e) { | ||
throw new RuntimeException("Could not load build output", e); | ||
} | ||
} | ||
|
||
private static Path getBuildOutputPath() { | ||
final Path buildDirectory = locateNativeImageBuildDirectory(); | ||
final File[] buildOutput = buildDirectory.toFile().listFiles((dir, name) -> name.toLowerCase(Locale.ROOT) | ||
.endsWith("-build-output-stats.json")); | ||
Assertions.assertNotNull(buildOutput, "Could not identify the native image build output"); | ||
Assertions.assertEquals(1, buildOutput.length, "Could not identify the native image build output"); | ||
return buildOutput[0].toPath(); | ||
} | ||
|
||
private static Path locateNativeImageBuildDirectory() { | ||
Path buildPath = Paths.get("target"); | ||
final File[] files = buildPath.toFile().listFiles((dir, name) -> name.toLowerCase(Locale.ROOT) | ||
.endsWith("-native-image-source-jar")); | ||
Assertions.assertNotNull(files, "Could not identify the native image build directory"); | ||
Assertions.assertEquals(1, files.length, "Could not identify the native image build directory"); | ||
return files[0].toPath(); | ||
} | ||
} |