quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.64k stars 2.64k forks source link

Native image agent integration with JVM mode tests #36822

Open galderz opened 11 months ago

galderz commented 11 months ago

Motivation

Quarkus users that want to integrate new libraries/components into native image process (e.g. smbj), or want to use JDK APIs that require extensive native image configuration to work (e.g. graphical user interfaces), face a considerable challenge coming up with the native image configuration to make their use cases work.

These users can tweak their applications to run in JVM mode with the native image agent in order to auto-generate native image configuration that will help them get a head start getting applications to work as native executables. However, this is both cumbersome to do and the configuration produced contains a lot of superfluous configuration that the Quarkus integration takes care of.

The aim of the Quarkus native image agent integration is to make the native image agent integration as seamless as possible and to make sure the configuration produced removes, as much as possible, configuration that Quarkus already takes care of.

Description

In this initial native image agent integration, the focus is on the Quarkus users being able to run JVM mode integration tests on Quarkus Maven applications transparently with the native image agent.

To do this, a container runtime is necessary because JVM mode integration tests will run using the JVM within the default Mandrel builder container image. This image contains the native image agent libraries required to produce native image configuration, hence avoiding the need for a local Mandrel or GraalVM installation.

NOTE: It is strongly recommended that subsequent native image builds that use the generated configuration are in-container, using the same Mandrel builder container image. Generating native image configuration with the same Mandrel image as the one that builds the native image is the best guarantee to avoid invalid configuration being produced.

With a container runtime running, invoke Maven's verify goal with -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent to run the JVM mode integration tests and generate the native image configuration. For example:

$ ./mvnw verify -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent
...
[INFO] --- failsafe:3.2.5:integration-test (default) @ new-project ---
...
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.acme.GreetingResourceIT
2024-04-02 12:55:52,837 INFO  [io.qua.tes.com.DefaultDockerContainerLauncher] (main) Executing "podman run --name quarkus-integration-test-BPkFA -i --rm --user 501:20 -p 8081:8081 -p 8444:8444 --entrypoint java -v /Users/galder/1/mendrugo/metadata/new-project/target:/project --env QUARKUS_LOG_CATEGORY__IO_QUARKUS__LEVEL=INFO --env QUARKUS_HTTP_PORT=8081 --env QUARKUS_HTTP_SSL_PORT=8444 --env TEST_URL=http://localhost:8081 --env QUARKUS_PROFILE=test-with-native-agent --env QUARKUS_TEST_INTEGRATION_TEST_PROFILE=test-with-native-agent quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 -agentlib:native-image-agent=access-filter-file=quarkus-access-filter.json,caller-filter-file=quarkus-caller-filter.json,config-output-dir=native-image-agent-base-config, -jar quarkus-app/quarkus-run.jar"
...
[INFO]
[INFO] --- quarkus:999-SNAPSHOT:native-image-agent (default) @ new-project ---
[INFO] Discovered native image agent generated files in /Users/galder/1/mendrugo/metadata/new-project/target/native-image-agent-final-config
...

$ cat ./new-project/target/native-image-agent-final-config/reflect-config.json
[
{
  "name":"org.acme.Alice",
  "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"sayMyName","parameterTypes":[] }]
},
{
  "name":"org.acme.Bob"
}
]

To achieve this output, the native image integration agent follows this process:

First, it generates native image agent configuration filters. These filters exclude commonly used packages in Quarkus applications because native image configurations related to those are not necessary to run the native executable. These filters are expressed in JSON format and are stored inside target/quarkus-access-filter.json and target/quarkus-caller-filter.json files.

Secondly, the java process that runs the integration tests is enhanced with the following JVM parameters. When the integration tests complete, the target/native-image-agent-base-config contains the native image configuration files generated by the agent:

-agentlib:native-image-agent=access-filter-file=quarkus-access-filter.json,caller-filter-file=quarkus-caller-filter.json,config-output-dir=native-image-agent-base-config

Thirdly, the filters generated in the first step cover most of the native image configuration use cases that Quarkus already takes care of, but resource configuration filtering is not fully working as expected (see oracle/graal#8417 for details). So, in this step the resource configuration is trimmed to remove unnecessary resource configuration. All the configuration is then transferred into the target/native-image-agent-final-config folder.

Informative By Default

By default the generated native image configuration files are not used by subsequent native image building processes. This precaution is taken to avoid situations where seemingly unrelated actions have unintended consequences on the native executable produced, e.g. disabling randomly failing tests.

By informing the developer of the generated native image configuration, they can copy/paste the output into files (or copy the files from the folder) to be stored under source control and evolve as needed. Ideally these files should be stored under the src/main/resources/META-INF/native-image/<group-id>/<artifact-id> folder, in which case the native image process will automatically pick them up.

It is possible to instruct Quarkus to optionally apply the generated native image configuration files into subsequent native image processes, by setting the -Dquarkus.native.agent-configuration-apply property. This can be useful to verify that the native integration tests work as expected, assuming that the JVM unit tests have generated the correct native image configuration.

If managing native image agent configuration files manually, it is highly recommended to regenerate them each time a Mandrel version update occurs, because the configuration necessary to make the application work might have varied due to internal Mandrel changes.

Debugging Native Image Agent Integration

If the generated native image agent configuration does not satisfy the needs of Quarkus users, they can obtain more information using any of the following techniques.

Debugging Filters

If native image agent is generating a configuration that it’s not working as expected, you should check that the configuration files include the expected information. For example, if some method happens to be accessed via reflection at runtime and you get an error, you want to verify that the configuration file contains a reflection entry for that method.

If the entry is missing, it could be that some call path is being filtered that maybe shouldn’t have been. To verify that, inspect the contents of target/quarkus-caller-filter.json and target/quarkus-access-filter.json files, and confirm that the class and/or package making the call or being accessed is not being filtered out.

If the missing entry is related to some resource, you should inspect the Quarkus build debug output and verify which resource patterns are being discarded, e.g.

$ ./mvnw -X verify -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent
...
[INFO] --- quarkus:999-SNAPSHOT:native-image-agent (default) @ new-project ---
...
[DEBUG] Discarding resources from native image configuration that match the following regular expression: .*(application.properties|java[.\/](lang|net|nio|time)|jakarta|jboss|logging|logging.properties|microprofile|quarkus|slf4j|smallrye|vertx).*
[DEBUG] Discarded included resource with pattern: \\QMETA-INF/microprofile-config.properties\\E
[DEBUG] Discarded included resource with pattern: \\QMETA-INF/services/io.quarkus.arc.ComponentsProvider\\E
...

Trace Output

A native image agent configuration trace output JSON file can be generated using the following additional system properties:

$ ./mvnw verify -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent -Dquarkus.test.native.agent.output.property.name=trace-output -Dquarkus.test.native.agent.output.property.value=native-image-agent-trace-file.json

When trace output is configured, no native image configuration is generated, and instead a target/native-image-agent-trace-file.json file is generated that contains trace information. For example:

$ tail ./target/native-image-agent-trace-file.json
{"tracer":"reflect", "function":"newInstance", "class":"java.lang.reflect.Array", "caller_class":"java.util.IdentityHashMap$KeySet", "result":"true", "args":["[Lio.netty.util.concurrent.FastThreadLocal;"]},
...

Unfortunately the trace output does not take into account the applied configuration filters, so the output contains all configuration decisions made by the agent. This is unlikely to change in the near future (see oracle/graal#7635).

Configuration With Origins (Experimental)

Alternative to the trace output, it is possible to configure the native image agent with an experimental flag that shows the origins of the configuration entries. You can enable that with the following additional system property:

$ ./mvnw clean test -Dnative-with-agent -Dquarkus.test.native.agent.additional.args=experimental-configuration-with-origins

The origins of the configuration entries can be found in text files inside the target/native-image-agent-base-config folder. For example:

$ tail ./target/native-image-agent-base-config/reflect-origins.txt
                                            └── org.acme.GreetingResource$quarkusrestinvoker$greeting_709ef95cd764548a2bbac83843a7f4cdd8077016#invoke(java.lang.Object,java.lang.Object[])
                                                └── org.acme.GreetingResource#greeting(java.lang.String)
                                                    └── org.acme.GreetingService_ClientProxy#greeting(java.lang.String)
                                                        └── org.acme.GreetingService#greeting(java.lang.String)
                                                            ├── java.lang.Class#forName(java.lang.String) - [ {   "name":"org.acme.Alice" }, {   "name":"org.acme.Bob" } ]
                                                            ├── java.lang.Class#getDeclaredConstructor(java.lang.Class[]) - [ {   "name":"org.acme.Alice",   "methods":[{"name":"<init>","parameterTypes":[] }] } ]
                                                            ├── java.lang.reflect.Constructor#newInstance(java.lang.Object[]) - [ {   "name":"org.acme.Alice",   "methods":[{"name":"<init>","parameterTypes":[] }] } ]
                                                            ├── java.lang.reflect.Method#invoke(java.lang.Object,java.lang.Object[]) - [ {   "name":"org.acme.Alice",   "methods":[{"name":"sayMyName","parameterTypes":[] }] } ]
                                                            └── java.lang.Class#getMethod(java.lang.String,java.lang.Class[]) - [ {   "name":"org.acme.Alice",   "methods":[{"name":"sayMyName","parameterTypes":[] }] } ]

Debugging With GDB

The native image agent itself is a native executable produced with GraalVM that uses JVMTI to intercept the calls that require native image configuration. As a last resort, it is possible to debug the native image agent with GDB, see here for instructions on how to do that.

Testing

A sample Maven project has been created that contains a pom.xml with the new profile under integration-tests/maven/target/test-classes/projects/native-agent-integration. The project includes an endpoint that will only succeed if reflection configuration has been generated and applied to the native executable. This is one use case where -Dquarkus.native.agent-configuration-apply is used to automate the process of applying the configuration and get confidence that the native image agent integration is working as expected.

Future Work

galderz commented 11 months ago

This work has uncovered a small bug in the resource pattern json production: https://github.com/quarkusio/quarkus/issues/36823

vsevel commented 10 months ago

nice work @galderz ! so when you run with profile native-with-agent, the native process being built after will include whatever has been gathered during jvm tests? so this will influence the native process we are building. it is not just about auditing. correct?

galderz commented 10 months ago

@vsevel Yeah, it's about running jvm mode tests with the agent, then taking the config that the agent has generated and feeding it into the native image process. There's some filtering we will be doing to avoid Quarkus and/or non-application related test configuration going in. See the linked draft for further discussions.

vsevel commented 7 months ago

I have tried, but could not get the -Dnative-with-agent option to produce an effect. I am running with:

$ java -version
openjdk version "21.0.2" 2024-01-16 LTS
OpenJDK Runtime Environment Temurin-21.0.2+13 (build 21.0.2+13-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.2+13 (build 21.0.2+13-LTS, mixed mode, sharing)

$ echo $JAVA_HOME
/e/softs/mandrel-java21-23.1.2.0-Final

$ mvn test -Dnative-with-agent -Denforcer.skip -o 

I can see I am using quarkus maven plugin 999-SNAPSHOT:

[INFO] --- quarkus-maven-plugin:999-SNAPSHOT:generate-code (default) @ bank-quarkus ---

and also

$ ls -al target/quarkus-app/lib/main | grep 999
-rw-r--r-- 1 Sevel 1049089  256101 Feb 27 15:37 io.quarkus.arc.arc-999-SNAPSHOT.jar
-rw-r--r-- 1 Sevel 1049089   47856 Feb 27 15:45 io.quarkus.quarkus-agroal-999-SNAPSHOT.jar
-rw-r--r-- 1 Sevel 1049089   78063 Feb 27 15:44 io.quarkus.quarkus-arc-999-SNAPSHOT.jar
-rw-r--r-- 1 Sevel 1049089   11440 Feb 27 16:00 io.quarkus.quarkus-avro-999-SNAPSHOT.jar
...

but I am not seeing any quarkus:999-SNAPSHOT:transform-native-image-agent-config how is this activated?

galderz commented 7 months ago

@vsevel If you want to try this out, as well as building the branch locally, you need to either create a new Quarkus maven project with the following command (you can adjust name, extensions...etc):

mvn io.quarkus:quarkus-maven-plugin:999-SNAPSHOT:create \
   -DprojectGroupId=org.acme \
   -DprojectArtifactId=my-project \
   -Dextensions='resteasy-reactive' \
   -DplatformVersion=999-SNAPSHOT

Or if you have an existing Quarkus project, once you modified the Quarkus version, add the following profile to the pom.xml:

    <profile>
      <id>native-with-agent</id>
      <activation>
        <property>
          <name>native-with-agent</name>
        </property>
      </activation>
      <build>
        <plugins>
          <plugin>
            <groupId>${quarkus.platform.group-id}</groupId>
            <artifactId>quarkus-maven-plugin</artifactId>
            <version>${quarkus.platform.version}</version>
            <extensions>true</extensions>
            <executions>
              <execution>
                <goals>
                  <goal>build</goal>
                  <goal>generate-code</goal>
                  <goal>generate-code-tests</goal>
                  <goal>generate-native-image-agent-filters</goal>
                  <goal>transform-native-image-agent-config</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>${surefire-plugin.version}</version>
            <configuration>
              <argLine>-agentlib:native-image-agent=access-filter-file=target/quarkus-access-filter.json,caller-filter-file=target/quarkus-caller-filter.json,${native.image.agent.output.property.name}=${project.build.directory}/${native.image.agent.output.value}${native.image.agent.additional.args}</argLine>
              <systemPropertyVariables>
                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                <maven.home>${maven.home}</maven.home>
              </systemPropertyVariables>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <properties>
        <native.image.agent.output.value>native-image-agent-base-config</native.image.agent.output.value>
        <skipITs>false</skipITs>
        <native.image.agent.output.property.name>config-output-dir</native.image.agent.output.property.name>
        <native.image.agent.additional.args>,</native.image.agent.additional.args>
        <quarkus.package.type>native</quarkus.package.type>
      </properties>
    </profile>
vsevel commented 7 months ago

it worked. thanks.

I am actually surprised with all that I see. I believe this app is working fine in native. so I do not understand why there are fasterxml classes, or caffeine, or arjuna, ... There were only 2 applicative classes: an interface and an impl class, which are used in a test using @InjectMock XService x;

the output:

[INFO] --- quarkus-maven-plugin:999-SNAPSHOT:transform-native-image-agent-config (default) @ bank-quarkus ---
[INFO] Discovered native image agent generated files
[INFO] Generated reflect-config.json:
[
{
  "name":"[Lcom.fasterxml.jackson.databind.deser.Deserializers;"
},
{
  "name":"[Lcom.fasterxml.jackson.databind.deser.KeyDeserializers;"
},
{
  "name":"[Lcom.fasterxml.jackson.databind.deser.ValueInstantiators;"
},
{
  "name":"[Lcom.fasterxml.jackson.databind.ser.Serializers;"
},
{
  "name":"[Lorg.apache.activemq.artemis.api.core.Pair;"
},
{
  "name":"[Lorg.apache.activemq.artemis.utils.collections.LinkedListImpl$Iterator;"
},
{
  "name":"[Lorg.apache.activemq.artemis.utils.collections.LinkedListImpl;"
},
{
  "name":"[Lorg.apache.avro.util.springframework.ConcurrentReferenceHashMap$Segment;"
},
{
  "name":"com.arjuna.ats.arjuna.common.CoordinatorEnvironmentBean",
  "allDeclaredFields":true,
  "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getCheckedActionFactory","parameterTypes":[] }, {"name":"getCheckedActionFactoryClassName","parameterTypes":[] }, {"name":"getCommunicationStore","parameterTypes":[] }, {"name":"getDefaultTimeout","parameterTypes":[] }, {"name":"getDynamic1PC","parameterTypes":[] }, {"name":"getMaxTwoPhaseCommitThreads","parameterTypes":[] }, {"name":"getTxReaperCancelFailWaitPeriod","parameterTypes":[] }, {"name":"getTxReaperCancelWaitPeriod","parameterTypes":[] }, {"name":"getTxReaperMode","parameterTypes":[] }, {"name":"getTxReaperTimeout","parameterTypes":[] }, {"name":"getTxReaperTraceGracePeriod","parameterTypes":[] }, {"name":"getTxReaperTraceInterval","parameterTypes":[] }, {"name":"getTxReaperZombieMax","parameterTypes":[] }, {"name":"isAllowCheckedActionFactoryOverride","parameterTypes":[] }, {"name":"isAlternativeRecordOrdering","parameterTypes":[] }, {"name":"isAsyncAfterSynchronization","parameterTypes":[] }, {"name":"isAsyncBeforeSynchronization","parameterTypes":[] }, {"name":"isAsyncCommit","parameterTypes":[] }, {"name":"isAsyncPrepare","parameterTypes":[] }, {"name":"isAsyncRollback","parameterTypes":[] }, {"name":"isBeforeCompletionWhenRollbackOnly","parameterTypes":[] }, {"name":"isClassicPrepare","parameterTypes":[] }, {"name":"isCommitOnePhase","parameterTypes":[] }, {"name":"isDynamic1PC","parameterTypes":[] }, {"name":"isEnableStatistics","parameterTypes":[] }, {"name":"isFinalizeBasicActions","parameterTypes":[] }, {"name":"isMaintainHeuristics","parameterTypes":[] }, {"name":"isReadonlyOptimisation","parameterTypes":[] }, {"name":"isSharedTransactionLog","parameterTypes":[] }, {"name":"isStartDisabled","parameterTypes":[] }, {"name":"isTransactionStatusManagerEnable","parameterTypes":[] }, {"name":"isWriteOptimisation","parameterTypes":[] }, {"name":"setAllowCheckedActionFactoryOverride","parameterTypes":["boolean"] }, {"name":"setAlternativeRecordOrdering","parameterTypes":["boolean"] }, {"name":"setAsyncAfterSynchronization","parameterTypes":["boolean"] }, {"name":"setAsyncBeforeSynchronization","parameterTypes":["boolean"] }, {"name":"setAsyncCommit","parameterTypes":["boolean"] }, {"name":"setAsyncPrepare","parameterTypes":["boolean"] }, {"name":"setAsyncRollback","parameterTypes":["boolean"] }, {"name":"setBeforeCompletionWhenRollbackOnly","parameterTypes":["boolean"] }, {"name":"setCheckedActionFactory","parameterTypes":["com.arjuna.ats.arjuna.coordinator.CheckedActionFactory"] }, {"name":"setCheckedActionFactoryClassName","parameterTypes":["java.lang.String"] }, {"name":"setClassicPrepare","parameterTypes":["boolean"] }, {"name":"setCommitOnePhase","parameterTypes":["boolean"] }, {"name":"setCommunicationStore","parameterTypes":["java.lang.String"] }, {"name":"setDefaultTimeout","parameterTypes":["int"] }, {"name":"setDynamic1PC","parameterTypes":["boolean"] }, {"name":"setEnableStatistics","parameterTypes":["boolean"] }, {"name":"setFinalizeBasicActions","parameterTypes":["boolean"] }, {"name":"setMaintainHeuristics","parameterTypes":["boolean"] }, {"name":"setMaxTwoPhaseCommitThreads","parameterTypes":["int"] }, {"name":"setReadonlyOptimisation","parameterTypes":["boolean"] }, {"name":"setSharedTransactionLog","parameterTypes":["boolean"] }, {"name":"setStartDisabled","parameterTypes":["boolean"] }, {"name":"setTransactionLog","parameterTypes":["boolean"] }, {"name":"setTransactionStatusManagerEnable","parameterTypes":["boolean"] }, {"name":"setTxReaperCancelFailWaitPeriod","parameterTypes":["long"] }, {"name":"setTxReaperCancelWaitPeriod","parameterTypes":["long"] }, {"name":"setTxReaperMode","parameterTypes":["java.lang.String"] }, {"name":"setTxReaperTimeout","parameterTypes":["long"] }, {"name":"setTxReaperTraceGracePeriod","parameterTypes":["long"] }, {"name":"setTxReaperTraceInterval","parameterTypes":["long"] }, {"name":"setTxReaperZombieMax","parameterTypes":["int"] }, {"name":"setWriteOptimisation","parameterTypes":["boolean"] }]
},
{
  "name":"com.arjuna.ats.arjuna.common.CoreEnvironmentBean",
  "allDeclaredFields":true,
  "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getNodeIdentifier","parameterTypes":[] }, {"name":"getPid","parameterTypes":[] }, {"name":"getProcessImplementation","parameterTypes":[] }, {"name":"getProcessImplementationClassName","parameterTypes":[] }, {"name":"getSocketProcessIdMaxPorts","parameterTypes":[] }, {"name":"getSocketProcessIdPort","parameterTypes":[] }, {"name":"getTimeoutFactor","parameterTypes":[] }, {"name":"getVarDir","parameterTypes":[] }, {"name":"isAllowMultipleLastResources","parameterTypes":[] }, {"name":"isDisableMultipleLastResourcesWarning","parameterTypes":[] }, {"name":"setAllowMultipleLastResources","parameterTypes":["boolean"] }, {"name":"setDisableMultipleLastResourcesWarning","parameterTypes":["boolean"] }, {"name":"setNODE_NAME_SIZE","parameterTypes":["int"] }, {"name":"setNodeIdentifier","parameterTypes":["java.lang.String"] }, {"name":"setPid","parameterTypes":["int"] }, {"name":"setProcessImplementation","parameterTypes":["com.arjuna.ats.arjuna.utils.Process"] }, {"name":"setProcessImplementationClassName","parameterTypes":["java.lang.String"] }, {"name":"setSocketProcessIdMaxPorts","parameterTypes":["int"] }, {"name":"setSocketProcessIdPort","parameterTypes":["int"] }, {"name":"setTimeoutFactor","parameterTypes":["int"] }, {"name":"setVarDir","parameterTypes":["java.lang.String"] }]
},
{
  "name":"com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean",
  "allDeclaredFields":true,
  "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getCacheStoreHash","parameterTypes":[] }, {"name":"getCacheStoreRemovedItems","parameterTypes":[] }, {"name":"getCacheStoreScanPeriod","parameterTypes":[] }, {"name":"getCacheStoreSize","parameterTypes":[] }, {"name":"getCacheStoreWorkItems","parameterTypes":[] }, {"name":"getCreateTable","parameterTypes":[] }, {"name":"getDropTable","parameterTypes":[] }, {"name":"getExposeAllLogRecordsAsMBeans","parameterTypes":[] }, {"name":"getHashedDirectories","parameterTypes":[] }, {"name":"getHierarchyRetry","parameterTypes":[] }, {"name":"getHierarchyTimeout","parameterTypes":[] }, {"name":"getJdbcAccess","parameterTypes":[] }, {"name":"getJmxToolingMBeanName","parameterTypes":[] }, {"name":"getLocalOSRoot","parameterTypes":[] }, {"name":"getObjectStoreDir","parameterTypes":[] }, {"name":"getObjectStoreType","parameterTypes":[] }, {"name":"getPurgeTime","parameterTypes":[] }, {"name":"getShare","parameterTypes":[] }, {"name":"getTablePrefix","parameterTypes":[] }, {"name":"getTxLogSize","parameterTypes":[] }, {"name":"isCacheStoreSync","parameterTypes":[] }, {"name":"isCreateTable","parameterTypes":[] }, {"name":"isDropTable","parameterTypes":[] }, {"name":"isExposeAllLogRecordsAsMBeans","parameterTypes":[] }, {"name":"isIgnoreMBeanHeuristics","parameterTypes":[] }, {"name":"isObjectStoreSync","parameterTypes":[] }, {"name":"isScanZeroLengthFiles","parameterTypes":[] }, {"name":"isSynchronousRemoval","parameterTypes":[] }, {"name":"isTransactionSync","parameterTypes":[] }, {"name":"isVolatileStoreSupportAllObjUids","parameterTypes":[] }, {"name":"setAndroidDirCheck","parameterTypes":["boolean"] }, {"name":"setCacheStoreHash","parameterTypes":["int"] }, {"name":"setCacheStoreRemovedItems","parameterTypes":["int"] }, {"name":"setCacheStoreScanPeriod","parameterTypes":["int"] }, {"name":"setCacheStoreSize","parameterTypes":["int"] }, {"name":"setCacheStoreSync","parameterTypes":["boolean"] }, {"name":"setCacheStoreWorkItems","parameterTypes":["int"] }, {"name":"setCreateTable","parameterTypes":["boolean"] }, {"name":"setDropTable","parameterTypes":["boolean"] }, {"name":"setExposeAllLogRecordsAsMBeans","parameterTypes":["boolean"] }, {"name":"setHashedDirectories","parameterTypes":["int"] }, {"name":"setHierarchyRetry","parameterTypes":["int"] }, {"name":"setHierarchyTimeout","parameterTypes":["int"] }, {"name":"setIgnoreMBeanHeuristics","parameterTypes":["boolean"] }, {"name":"setJdbcAccess","parameterTypes":["java.lang.String"] }, {"name":"setJdbcStoreDataSource","parameterTypes":["javax.sql.DataSource"] }, {"name":"setJmxToolingMBeanName","parameterTypes":["java.lang.String"] }, {"name":"setLocalOSRoot","parameterTypes":["java.lang.String"] }, {"name":"setObjectStoreDir","parameterTypes":["java.lang.String"] }, {"name":"setObjectStoreSync","parameterTypes":["boolean"] }, {"name":"setObjectStoreType","parameterTypes":["java.lang.String"] }, {"name":"setPurgeTime","parameterTypes":["long"] }, {"name":"setScanZeroLengthFiles","parameterTypes":["boolean"] }, {"name":"setShare","parameterTypes":["int"] }, {"name":"setSynchronousRemoval","parameterTypes":["boolean"] }, {"name":"setTablePrefix","parameterTypes":["java.lang.String"] }, {"name":"setTransactionSync","parameterTypes":["boolean"] }, {"name":"setTxLogSize","parameterTypes":["long"] }, {"name":"setVolatileStoreSupportAllObjUids","parameterTypes":["boolean"] }]
},
{
  "name":"com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean",
  "allDeclaredFields":true,
  "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getExpiryScanInterval","parameterTypes":[] }, {"name":"getExpiryScannerClassNames","parameterTypes":[] }, {"name":"getExpiryScanners","parameterTypes":[] }, {"name":"getPeriodicRecoveryInitilizationOffset","parameterTypes":[] }, {"name":"getPeriodicRecoveryPeriod","parameterTypes":[] }, {"name":"getRecoveryActivatorClassNames","parameterTypes":[] }, {"name":"getRecoveryActivators","parameterTypes":[] }, {"name":"getRecoveryAddress","parameterTypes":[] }, {"name":"getRecoveryBackoffPeriod","parameterTypes":[] }, {"name":"getRecoveryModuleClassNames","parameterTypes":[] }, {"name":"getRecoveryModules","parameterTypes":[] }, {"name":"getRecoveryPort","parameterTypes":[] }, {"name":"getTransactionStatusManagerAddress","parameterTypes":[] }, {"name":"getTransactionStatusManagerExpiryTime","parameterTypes":[] }, {"name":"getTransactionStatusManagerPort","parameterTypes":[] }, {"name":"isRecoveryListener","parameterTypes":[] }, {"name":"isTimeoutSocket","parameterTypes":[] }, {"name":"setExpiryScanInterval","parameterTypes":["int"] }, {"name":"setExpiryScannerClassNames","parameterTypes":["java.util.List"] }, {"name":"setExpiryScanners","parameterTypes":["java.util.List"] }, {"name":"setPeriodicRecoveryInitilizationOffset","parameterTypes":["int"] }, {"name":"setPeriodicRecoveryPeriod","parameterTypes":["int"] }, {"name":"setRecoveryActivatorClassNames","parameterTypes":["java.util.List"] }, {"name":"setRecoveryActivators","parameterTypes":["java.util.List"] }, {"name":"setRecoveryAddress","parameterTypes":["java.lang.String"] }, {"name":"setRecoveryBackoffPeriod","parameterTypes":["int"] }, {"name":"setRecoveryListener","parameterTypes":["boolean"] }, {"name":"setRecoveryModuleClassNames","parameterTypes":["java.util.List"] }, {"name":"setRecoveryModules","parameterTypes":["java.util.List"] }, {"name":"setRecoveryPort","parameterTypes":["int"] }, {"name":"setTimeoutSocket","parameterTypes":["boolean"] }, {"name":"setTransactionStatusManagerAddress","parameterTypes":["java.lang.String"] }, {"name":"setTransactionStatusManagerExpiryTime","parameterTypes":["int"] }, {"name":"setTransactionStatusManagerPort","parameterTypes":["int"] }]
},
{
  "name":"com.arjuna.ats.internal.arjuna.coordinator.CheckedActionFactoryImple",
  "queryAllPublicConstructors":true,
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"com.arjuna.ats.internal.arjuna.utils.SocketProcessId",
  "queryAllPublicConstructors":true,
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple",
  "queryAllPublicConstructors":true,
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"com.arjuna.ats.internal.jta.transaction.arjunacore.UserTransactionImple",
  "queryAllPublicConstructors":true,
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"com.arjuna.ats.jta.common.JTAEnvironmentBean",
  "allDeclaredFields":true,
  "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getAsyncCommitPoolSize","parameterTypes":[] }, {"name":"getCommitMarkableResourceJNDINames","parameterTypes":[] }, {"name":"getCommitMarkableResourceRecordDeleteBatchSize","parameterTypes":[] }, {"name":"getCommitMarkableResourceRecordDeleteBatchSizeMap","parameterTypes":[] }, {"name":"getCommitMarkableResourceTableNameMap","parameterTypes":[] }, {"name":"getLastResourceOptimisationInterface","parameterTypes":[] }, {"name":"getLastResourceOptimisationInterfaceClassName","parameterTypes":[] }, {"name":"getOrphanSafetyInterval","parameterTypes":[] }, {"name":"getPerformImmediateCleanupOfCommitMarkableResourceBranchesMap","parameterTypes":[] }, {"name":"getTransactionManager","parameterTypes":[] }, {"name":"getTransactionManagerClassName","parameterTypes":[] }, {"name":"getTransactionManagerJNDIContext","parameterTypes":[] }, {"name":"getTransactionSynchronizationRegistry","parameterTypes":[] }, {"name":"getTransactionSynchronizationRegistryClassName","parameterTypes":[] }, {"name":"getTransactionSynchronizationRegistryJNDIContext","parameterTypes":[] }, {"name":"getUserTransaction","parameterTypes":[] }, {"name":"getUserTransactionClassName","parameterTypes":[] }, {"name":"getUserTransactionJNDIContext","parameterTypes":[] }, {"name":"getUserTransactionOperationsProviderClassName","parameterTypes":[] }, {"name":"getXaRecoveryNodes","parameterTypes":[] }, {"name":"getXaResourceIsSameRMClassNames","parameterTypes":[] }, {"name":"getXaResourceMapClassNames","parameterTypes":[] }, {"name":"getXaResourceMaps","parameterTypes":[] }, {"name":"getXaResourceOrphanFilterClassNames","parameterTypes":[] }, {"name":"getXaResourceOrphanFilters","parameterTypes":[] }, {"name":"getXaResourceRecordWrappingPluginClassName","parameterTypes":[] }, {"name":"getXaResourceRecoveries","parameterTypes":[] }, {"name":"getXaResourceRecoveryClassNames","parameterTypes":[] }, {"name":"isNotifyCommitMarkableResourceRecoveryModuleOfCompleteBranches","parameterTypes":[] }, {"name":"isPerformImmediateCleanupOfCommitMarkableResourceBranches","parameterTypes":[] }, {"name":"isStrictJTA12DuplicateXAENDPROTOErr","parameterTypes":[] }, {"name":"isSupportSubtransactions","parameterTypes":[] }, {"name":"isTransactionToThreadListenersEnabled","parameterTypes":[] }, {"name":"isXaAssumeRecoveryComplete","parameterTypes":[] }, {"name":"isXaRollbackOptimization","parameterTypes":[] }, {"name":"isXaTransactionTimeoutEnabled","parameterTypes":[] }, {"name":"setAsyncCommitPoolSize","parameterTypes":["int"] }, {"name":"setCommitMarkableResourceJNDINames","parameterTypes":["java.util.List"] }, {"name":"setCommitMarkableResourceRecordDeleteBatchSize","parameterTypes":["int"] }, {"name":"setCommitMarkableResourceRecordDeleteBatchSizeMap","parameterTypes":["java.util.Map"] }, {"name":"setCommitMarkableResourceTableName","parameterTypes":["java.lang.String"] }, {"name":"setCommitMarkableResourceTableNameMap","parameterTypes":["java.util.Map"] }, {"name":"setDefaultTransactionOperationsProviderClassName","parameterTypes":["java.lang.String"] }, {"name":"setLastResourceOptimisationInterface","parameterTypes":["java.lang.Class"] }, {"name":"setLastResourceOptimisationInterfaceClassName","parameterTypes":["java.lang.String"] }, {"name":"setNotifyCommitMarkableResourceRecoveryModuleOfCompleteBranches","parameterTypes":["boolean"] }, {"name":"setOrphanSafetyInterval","parameterTypes":["int"] }, {"name":"setPerformImmediateCleanupOfCommitMarkableResourceBranches","parameterTypes":["boolean"] }, {"name":"setPerformImmediateCleanupOfCommitMarkableResourceBranchesMap","parameterTypes":["java.util.Map"] }, {"name":"setStrictJTA12DuplicateXAENDPROTOErr","parameterTypes":["boolean"] }, {"name":"setSupportSubtransactions","parameterTypes":["boolean"] }, {"name":"setTransactionManager","parameterTypes":["jakarta.transaction.TransactionManager"] }, {"name":"setTransactionManagerClassName","parameterTypes":["java.lang.String"] }, {"name":"setTransactionManagerJNDIContext","parameterTypes":["java.lang.String"] }, {"name":"setTransactionSynchronizationRegistry","parameterTypes":["jakarta.transaction.TransactionSynchronizationRegistry"] }, {"name":"setTransactionSynchronizationRegistryClassName","parameterTypes":["java.lang.String"] }, {"name":"setTransactionSynchronizationRegistryJNDIContext","parameterTypes":["java.lang.String"] }, {"name":"setTransactionToThreadListenersEnabled","parameterTypes":["boolean"] }, {"name":"setUserTransaction","parameterTypes":["jakarta.transaction.UserTransaction"] }, {"name":"setUserTransactionClassName","parameterTypes":["java.lang.String"] }, {"name":"setUserTransactionJNDIContext","parameterTypes":["java.lang.String"] }, {"name":"setUserTransactionOperationsProvider","parameterTypes":["org.jboss.tm.usertx.UserTransactionOperationsProvider"] }, {"name":"setUserTransactionOperationsProviderClassName","parameterTypes":["java.lang.String"] }, {"name":"setXaAssumeRecoveryComplete","parameterTypes":["boolean"] }, {"name":"setXaRecoveryNodes","parameterTypes":["java.util.List"] }, {"name":"setXaResourceIsSameRMClassNames","parameterTypes":["java.util.List"] }, {"name":"setXaResourceMapClassNames","parameterTypes":["java.util.List"] }, {"name":"setXaResourceMaps","parameterTypes":["java.util.List"] }, {"name":"setXaResourceOrphanFilterClassNames","parameterTypes":["java.util.List"] }, {"name":"setXaResourceOrphanFilters","parameterTypes":["java.util.List"] }, {"name":"setXaResourceRecordWrappingPlugin","parameterTypes":["com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecordWrappingPlugin"] }, {"name":"setXaResourceRecordWrappingPluginClassName","parameterTypes":["java.lang.String"] }, {"name":"setXaResourceRecoveries","parameterTypes":["java.util.List"] }, {"name":"setXaResourceRecoveryClassNames","parameterTypes":["java.util.List"] }, {"name":"setXaRollbackOptimization","parameterTypes":["boolean"] }, {"name":"setXaTransactionTimeoutEnabled","parameterTypes":["boolean"] }]
},
{
  "name":"com.arjuna.ats.jta.resources.LastResourceCommitOptimisation"
},
{
  "name":"com.fasterxml.jackson.core.JsonFactory"
},
{
  "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"com.github.benmanes.caffeine.cache.BLCHeader$DrainStatusRef",
  "fields":[{"name":"drainStatus"}]
},
{
  "name":"com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueColdProducerFields",
  "fields":[{"name":"producerLimit"}]
},
{
  "name":"com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueConsumerFields",
  "fields":[{"name":"consumerIndex"}]
},
{
  "name":"com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueProducerFields",
  "fields":[{"name":"producerIndex"}]
},
{
  "name":"com.github.benmanes.caffeine.cache.BoundedLocalCache",
  "fields":[{"name":"refreshes"}]
},
{
  "name":"com.github.benmanes.caffeine.cache.PS",
  "fields":[{"name":"key"}, {"name":"value"}]
},
{
  "name":"com.github.benmanes.caffeine.cache.PSW",
  "fields":[{"name":"writeTime"}],
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"com.github.benmanes.caffeine.cache.SSW",
  "methods":[{"name":"<init>","parameterTypes":["com.github.benmanes.caffeine.cache.Caffeine","com.github.benmanes.caffeine.cache.AsyncCacheLoader","boolean"] }]
},
{
  "name":"com.github.benmanes.caffeine.cache.UnboundedLocalCache",
  "fields":[{"name":"refreshes"}]
},
{
  "name":"com.ibm.lang.management.OperatingSystemMXBean"
},
{
  "name":"com.sun.management.GarbageCollectionNotificationInfo"
},
{
  "name":"com.sun.management.OperatingSystemMXBean",
  "methods":[{"name":"getCpuLoad","parameterTypes":[] }, {"name":"getProcessCpuLoad","parameterTypes":[] }]
},
{
  "name":"com.sun.management.UnixOperatingSystemMXBean"
},
{
  "name":"io.confluent.kafka.serializers.KafkaAvroDeserializer",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"io.confluent.kafka.serializers.KafkaAvroSerializer",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"io.confluent.kafka.serializers.context.NullContextNameStrategy",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"io.confluent.kafka.serializers.subject.TopicNameStrategy",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"io.opentelemetry.exporter.internal.InstrumentationUtil",
  "methods":[{"name":"shouldSuppressInstrumentation","parameterTypes":["io.opentelemetry.context.Context"] }]
},
{
  "name":"io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueConsumerIndexField",
  "fields":[{"name":"consumerIndex"}]
},
{
  "name":"io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueProducerIndexField",
  "fields":[{"name":"producerIndex"}]
},
{
  "name":"io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueProducerLimitField",
  "fields":[{"name":"producerLimit"}]
},
{
  "name":"io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder",
  "methods":[{"name":"setExemplarFilter","parameterTypes":["io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter"] }]
},
{
  "name":"java.io.Serializable",
  "queryAllDeclaredMethods":true
},
{
  "name":"java.lang.Class",
  "methods":[{"name":"getModule","parameterTypes":[] }]
},
{
  "name":"java.lang.Module",
  "methods":[{"name":"isNamed","parameterTypes":[] }]
},
{
  "name":"java.lang.Object",
  "allDeclaredFields":true,
  "queryAllDeclaredMethods":true,
  "queryAllDeclaredConstructors":true,
  "methods":[{"name":"clone","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }]
},
{
  "name":"java.lang.Runtime",
  "methods":[{"name":"version","parameterTypes":[] }]
},
{
  "name":"java.lang.Runtime$Version",
  "methods":[{"name":"feature","parameterTypes":[] }]
},
{
  "name":"java.security.AccessController"
},
{
  "name":"java.sql.Date"
},
{
  "name":"java.sql.Timestamp"
},
{
  "name":"java.util.concurrent.CompletionException"
},
{
  "name":"javafx.beans.value.ObservableValue"
},
{
  "name":"javax.xml.stream.XMLInputFactory"
},
{
  "name":"org.apache.activemq.artemis.api.core.client.loadbalance.RoundRobinConnectionLoadBalancingPolicy",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle_impl",
  "methods":[{"name":"<init>","parameterTypes":["org.slf4j.Logger"] }]
},
{
  "name":"org.apache.activemq.artemis.core.protocol.hornetq.client.HornetQClientProtocolManagerFactory",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.activemq.artemis.ra.ActiveMQRALogger_impl",
  "methods":[{"name":"<init>","parameterTypes":["org.slf4j.Logger"] }]
},
{
  "name":"org.apache.kafka.clients.consumer.CooperativeStickyAssignor",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.kafka.clients.consumer.RangeAssignor",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.kafka.common.metrics.stats.CumulativeCount"
},
{
  "name":"org.apache.kafka.common.metrics.stats.CumulativeSum"
},
{
  "name":"org.apache.kafka.common.serialization.StringDeserializer",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.kafka.common.serialization.StringSerializer",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.hamcrest.core.StringStartsWith",
  "queryAllDeclaredMethods":true
},
{
  "name":"org.hamcrest.core.SubstringMatcher",
  "queryAllDeclaredMethods":true
},
{
  "name":"org.hamcrest.text.MatchesPattern",
  "queryAllDeclaredMethods":true
},
{
  "name":"org.testcontainers.dockerclient.NpipeSocketClientProviderStrategy",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"sun.misc.Unsafe",
  "fields":[{"name":"theUnsafe"}],
  "methods":[{"name":"getAndAddLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"getAndSetObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }]
}
]
[INFO] Generated serialization-config.json:
{
  "types":[
    {
      "name":"java.io.File"
    },
    {
      "name":"java.lang.Object[]"
    },
    {
      "name":"java.lang.String"
    },
    {
      "name":"java.util.ArrayList"
    },
    {
      "name":"java.util.Arrays$ArrayList"
    },
    {
      "name":"java.util.CollSer"
    },
    {
      "name":"java.util.Collections$EmptyList"
    },
    {
      "name":"java.util.Collections$EmptyMap"
    },
    {
      "name":"java.util.Collections$UnmodifiableCollection"
    },
    {
      "name":"java.util.Collections$UnmodifiableList"
    },
    {
      "name":"java.util.Collections$UnmodifiableMap"
    },
    {
      "name":"java.util.HashMap"
    },
    {
      "name":"java.util.HashSet"
    }
  ],
  "lambdaCapturingTypes":[
  ],
  "proxies":[
  ]
}
[INFO] Generated jni-config.json:
[
{
  "name":"sun.instrument.InstrumentationImpl",
  "methods":[{"name":"<init>","parameterTypes":["long","boolean","boolean","boolean"] }, {"name":"loadClassAndCallAgentmain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"loadClassAndCallPremain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"transform","parameterTypes":["java.lang.Module","java.lang.ClassLoader","java.lang.String","java.lang.Class","java.security.ProtectionDomain","byte[]","boolean"] }]
}
]
[INFO] Generated proxy-config.json:
[
]
[INFO] Generated resource-config.json:
{
  "bundles":[
    {
      "name":"ContributorValidationMessages"
    },
    {
      "name":"ValidationMessages"
    },
    {
      "locales":[
        "",
        "en"
      ],
      "name":"org.hibernate.validator.ValidationMessages"
    }
  ],
  "resources":{
    "includes":[
      {
        "pattern":"\\QMETA-INF/openapi.json\\E"
      },
      {
        "pattern":"\\QMETA-INF/openapi.yaml\\E"
      },
      {
        "pattern":"\\QMETA-INF/openapi.yml\\E"
      },
      {
        "pattern":"\\QMETA-INF/validation.xml\\E"
      },
      {
        "pattern":"\\QWEB-INF/classes/META-INF/openapi.json\\E"
      },
      {
        "pattern":"\\QWEB-INF/classes/META-INF/openapi.yaml\\E"
      },
      {
        "pattern":"\\QWEB-INF/classes/META-INF/openapi.yml\\E"
      },
      {
        "pattern":"\\Qapplication.yaml\\E"
      },
      {
        "pattern":"\\Qapplication.yml\\E"
      },
      {
        "pattern":"\\Qdocker-java.properties\\E"
      },
      {
        "pattern":"\\Qjacoco-agent.properties\\E"
      },
      {
        "pattern":"\\Qjava/security/Permission.class\\E"
      },
      {
        "pattern":"\\Qjava/security/Principal.class\\E"
      },
      {
        "pattern":"\\Qjavax/transaction/xa/XAResource.class\\E"
      },
      {
        "pattern":"\\Qjndi.properties\\E"
      },
      {
        "pattern":"\\Qmockito-extensions/org.mockito.plugins.AnnotationEngine\\E"
      },
      {
        "pattern":"\\Qmockito-extensions/org.mockito.plugins.DoNotMockEnforcerWithType\\E"
      },
      {
        "pattern":"\\Qmockito-extensions/org.mockito.plugins.DoNotMockEnforcer\\E"
      },
      {
        "pattern":"\\Qmockito-extensions/org.mockito.plugins.InstantiatorProvider2\\E"
      },
      {
        "pattern":"\\Qmockito-extensions/org.mockito.plugins.MockResolver\\E"
      },
      {
        "pattern":"\\Qmockito-extensions/org.mockito.plugins.MockitoLogger\\E"
      },
      {
        "pattern":"\\Qmockito-extensions/org.mockito.plugins.PluginSwitch\\E"
      },
      {
        "pattern":"\\Qmockito-extensions/org.mockito.plugins.StackTraceCleanerProvider\\E"
      },
      {
        "pattern":"\\Qorg.apache.activemq.artemis.api.jms.ActiveMQJMSClient.properties\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/branchfc.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/branchnc.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/branchpc.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/bundle.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/class.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/down.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/greenbar.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/group.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/method.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/package.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/prettify.css\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/prettify.js\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/redbar.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/report.css\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/report.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/session.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/sort.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/sort.js\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/source.gif\\E"
      },
      {
        "pattern":"\\Qorg/jacoco/report/internal/html/resources/up.gif\\E"
      },
      {
        "pattern":"\\Qtestcontainers.properties\\E"
      },
      {
        "pattern":"java.base:\\Qjava/security/Permission.class\\E"
      },
      {
        "pattern":"java.base:\\Qjava/security/Principal.class\\E"
      },
      {
        "pattern":"java.base:\\Qsun/net/idn/uidna.spp\\E"
      },
      {
        "pattern":"java.transaction.xa:\\Qjavax/transaction/xa/XAResource.class\\E"
      },
      {
        "pattern":"jdk.jfr:\\Qjdk/jfr/internal/query/view.ini\\E"
      }
    ]
  }
}
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:42 min
[INFO] Finished at: 2024-02-28T10:24:35+01:00
[INFO] ------------------------------------------------------------------------
galderz commented 7 months ago

I am actually surprised with all that I see. I believe this app is working fine in native. so I do not understand why there are fasterxml classes, or caffeine, or arjuna, ... There were only 2 applicative classes: an interface and an impl class, which are used in a test using @InjectMock XService x;

Right, we're still fine tuning things so I would need to see what the causes for these registrations are to be able to explain them better. If this is a project you can share with me I can look into it. Otherwise if you can generate trace output and configuration origins (as explained in the description), I can try to see if I can filter things further.

A note about resource configuration. Filtering before the native image agent runs doesn't work that well (see https://github.com/oracle/graal/issues/8417), so I'm doing post-filtering here. I can enhance that filter with some of the things I see in your output, but it would help to make sure I'm doing this right with a trace output and configuration origins.

Then, about serialization, I've tried to remove everything except any JDK types because I can't fully guarantee they are not in use unless they're filtered by the agent itself with the filters we provide to it.

vsevel commented 7 months ago

I executed mvn -X clean test -Dnative-with-agent -Denforcer.skip=true -o -Dnative.image.agent.additional.args=experimental-configuration-with-origins a directory target/native-image-agent-base-configexperimental-configuration-with-origins was created, with content:

$ ls -al target/native-image-agent-base-configexperimental-configuration-with-origins
total 67
drwxr-xr-x 1 Sevel 1049089     0 Feb 29 09:27 .
drwxr-xr-x 1 Sevel 1049089     0 Feb 29 09:27 ..
drwxr-xr-x 1 Sevel 1049089     0 Feb 29 09:25 agent-extracted-predefined-classes
-rw-r--r-- 1 Sevel 1049089   508 Feb 29 09:27 jni-config.json
-rw-r--r-- 1 Sevel 1049089    65 Feb 29 09:27 predefined-classes-config.json
-rw-r--r-- 1 Sevel 1049089     4 Feb 29 09:27 proxy-config.json
-rw-r--r-- 1 Sevel 1049089 26356 Feb 29 09:27 reflect-config.json
-rw-r--r-- 1 Sevel 1049089 23490 Feb 29 09:27 resource-config.json
-rw-r--r-- 1 Sevel 1049089  1123 Feb 29 09:27 serialization-config.json

no reflect-origins.txt :(

vsevel commented 7 months ago

I made the relevant files available here

galderz commented 6 months ago

@vsevel I have updated the PR. Could you please have a go again and see what things look like now, and see if the issues you spotted are gone?

vsevel commented 6 months ago

I won't be able to work on this topic before 2 weeks (but I won't forget :-)

vsevel commented 5 months ago

can't start the app during the IT test. I execute:

mvn verify -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent -Dquarkus.native.container-build=true

it launches:

[INFO] Running org.acme.GreetingResourceIT
2024-04-24 08:39:59,931 INFO  [io.qua.tes.com.DefaultDockerContainerLauncher] (main) Executing "docker run --name quarkus-integration-test-BeyxL -i
 --rm -p 8081:8081 -p 8444:8444 --entrypoint java -v /e/tmp/quarkus/galder/galdertestnative/target:/project:z --env QUARKUS_LOG_CATEGORY__IO_QUARKU
S__LEVEL=INFO --env QUARKUS_HTTP_PORT=8081 --env QUARKUS_HTTP_SSL_PORT=8444 --env TEST_URL=http://localhost:8081 --env QUARKUS_PROFILE=test-with-na
tive-agent --env QUARKUS_TEST_INTEGRATION_TEST_PROFILE=test-with-native-agent --env QUARKUS_NATIVE_CONTAINER_BUILD=true quay.io/quarkus/ubi-quarkus
-mandrel-builder-image:jdk-21 -agentlib:native-image-agent=access-filter-file=quarkus-access-filter.json,caller-filter-file=quarkus-caller-filter.j
son,config-output-dir=native-image-agent-base-config, -jar quarkus-app\quarkus-run.jar"
2024-04-24 08:39:59,955 INFO  [io.qua.tes.com.DefaultDockerContainerLauncher] (main) Wait for server to start by capturing listening data...
Waited 60 seconds for target\quarkus.log to contain info about the listening port and protocol but no such info was found. Check if the options qua
rkus.log.level and quarkus.log.file.level are at least INFO (or more verbose).
Failed to launch the application. The application logs can be found at: E:\tmp\quarkus\galder\galdertestnative\target\quarkus.log

the quarkus.log says: Error: Unable to access jarfile quarkus-app\quarkus-run.jar

note: in the target dir, I do have the quarkus-access-filter.json and quarkus-caller-filter.json

there must be an option I am missing on the docker command. for instance am I supposed to have a -w?

galderz commented 5 months ago

Assuming the target folder does indeed have quarkus-app\quarkus-run.jar, this could be due to some of magic required to get volumes working on Windows. I'll see what I can find out.

vsevel commented 5 months ago

I tried on a linux box @galderz I created a simple projet, and executed:

 mvn verify -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent

a directory was created with name /target/native-image-agent-base-config in reflect-config.json I see:

[
{
  "name":"java.lang.Thread",
  "fields":[{"name":"threadLocalRandomProbe"}]
},
{
  "name":"java.security.SecureRandomParameters"
},
{
  "name":"java.util.concurrent.ForkJoinTask",
  "fields":[{"name":"aux"}, {"name":"status"}]
},
{
  "name":"java.util.concurrent.atomic.AtomicBoolean",
  "fields":[{"name":"value"}]
},
{
  "name":"java.util.concurrent.atomic.AtomicReference",
  "fields":[{"name":"value"}]
},
{
  "name":"java.util.concurrent.atomic.Striped64",
  "fields":[{"name":"base"}, {"name":"cellsBusy"}]
},
{
  "name":"javax.crac.Resource"
},
{
  "name":"jdk.crac.Resource"
},
{
  "name":"jdk.internal.misc.Unsafe"
},
{
  "name":"sun.security.provider.NativePRNG",
  "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
},
{
  "name":"sun.security.provider.SHA",
  "methods":[{"name":"<init>","parameterTypes":[] }]
}
]

I did not see much in the logs:

[INFO] Running org.acme.GreetingResourceIT
2024-04-25 12:57:39,478 INFO  [io.qua.tes.com.DefaultDockerContainerLauncher] (main) Executing "docker run --name quarkus-integration-test-wedpI -i --rm --user 2002:2002 -p 8081:8081 -p 8444:8444 --entrypoint java -v /export/soft/appl/galder/galderapp/target:/project:z --env QUARKUS_LOG_CATEGORY__IO_QUARKUS__LEVEL=INFO --env QUARKUS_HTTP_PORT=8081 --env QUARKUS_HTTP_SSL_PORT=8444 --env TEST_URL=http://localhost:8081 --env QUARKUS_PROFILE=test-with-native-agent --env QUARKUS_TEST_INTEGRATION_TEST_PROFILE=test-with-native-agent quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 -agentlib:native-image-agent=access-filter-file=quarkus-access-filter.json,caller-filter-file=quarkus-caller-filter.json,config-output-dir=native-image-agent-base-config, -jar quarkus-app/quarkus-run.jar"
2024-04-25 12:57:39,484 INFO  [io.qua.tes.com.DefaultDockerContainerLauncher] (main) Wait for server to start by capturing listening data...
2024-04-25 12:58:01,565 INFO  [io.qua.tes.com.DefaultDockerContainerLauncher] (main) Server started on port 8081
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 25.95 s -- in org.acme.GreetingResourceIT
2024-04-25 12:58:03,019 INFO  [io.qua.tes.com.DefaultDockerContainerLauncher] (main) Close the container

is it supposed to push something in the logs? what are the produced filtered files that contain elements that were not already configured by quarkus for my application?

galderz commented 5 months ago

is it supposed to push something in the logs? what are the produced filtered files that contain elements that were not already configured by quarkus for my application?

No, the logs won't show anything any more. As noted in my comment in https://github.com/quarkusio/quarkus/pull/36826#issuecomment-2031940548, a CI job can do this after the JVM integration tests have run and display them nicely (e.g. with jq).

I'm still looking at the windows issue.

galderz commented 5 months ago

Also, the reflection configuration that you see looks right. There are some additional registrations that originate in the JDK but we can't really filter those without affecting other use cases down the line.

vsevel commented 5 months ago

Also, the reflection configuration that you see looks right. There are some additional registrations that originate in the JDK but we can't really filter those without affecting other use cases down the line.

ok. so all the exported and filtered files are in native-image-agent-base-config

No, the logs won't show anything any more. As noted in my comment in https://github.com/quarkusio/quarkus/pull/36826#issuecomment-2031940548, a CI job can do this after the JVM integration tests have run and display them nicely (e.g. with jq).

fair enough

I'm still looking at the windows issue.

this would be nice, because we might position it as a dev tool, rather than a CI tool. i.e. documenting how to run these commands from the (windows) developer machine.

vsevel commented 5 months ago

I was able to run the docker command on a windows prompt with:

docker run --name quarkus-integration-test-ZsNZD -i --rm -p 8081:8081 -p 8444:8444 --entrypoint java -v e:\tmp\quarkus\galder\galdertestnative\target:/project:z --env QUARKUS_LOG_CATEGORY__IO_QUARKUS__LEVEL=INFO --env QUARKUS_HTTP_PORT=8081 --env QUARKUS_HTTP_SSL_PORT=8444 --env TEST_URL=http://localhost:8081 --env QUARKUS_PROFILE=test-with-native-agent --env QUARKUS_TEST_INTEGRATION_TEST_PROFILE=test-with-native-agent --env QUARKUS_NATIVE_CONTAINER_BUILD=true quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 -agentlib:native-image-agent=access-filter-file=quarkus-access-filter.json,caller-filter-file=quarkus-caller-filter.json,config-output-dir=native-image-agent-base-config, -jar quarkus-app/quarkus-run.jar

instead of (generated by verify command):

docker run --name quarkus-integration-test-YlUrp -i --rm -p 8081:8081 -p 8444:8444 --entrypoint java -v /e/tmp/quarkus/galder/galdertestnative/target:/project:z --env QUARKUS_LOG_CATEGORY__IO_QUARKUS__LEVEL=INFO --env QUARKUS_HTTP_PORT=8081 --env QUARKUS_HTTP_SSL_PORT=8444 --env TEST_URL=http://localhost:8081 --env QUARKUS_PROFILE=test-with-native-agent --env QUARKUS_TEST_INTEGRATION_TEST_PROFILE=test-with-native-agent --env QUARKUS_NATIVE_CONTAINER_BUILD=true quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 -agentlib:native-image-agent=access-filter-file=quarkus-access-filter.json,caller-filter-file=quarkus-caller-filter.json,config-output-dir=native-image-agent-base-config, -jar quarkus-app\quarkus-run.jar

the 2 changes are:

with those changes, the app starts successfully:

2024-04-25 15:02:19,660 INFO  [io.quarkus] (main) galdertestnative 1.0.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 4.265s. Listening on: http://0.0.0.0:8081
2024-04-25 15:02:19,665 INFO  [io.quarkus] (main) Profile test-with-native-agent activated.
2024-04-25 15:02:19,665 INFO  [io.quarkus] (main) Installed features: [cdi, rest, smallrye-context-propagation, vertx]

funny enough, I can't get this to work from git bash.

vsevel commented 5 months ago

finally got it to work on git bash (and that's ugly):

docker run --name quarkus-integration-test-FRxQj -i --rm -p 8081:8081 -p 8444:8444 --entrypoint java -v /$(pwd)/target:/project:z --env QUARKUS_LOG_CATEGORY__IO_QUARKUS__LEVEL=INFO --env QUARKUS_HTTP_PORT=8081 --env QUARKUS_HTTP_SSL_PORT=8444 --env TEST_URL=http://localhost:8081 --env QUARKUS_PROFILE=test-with-native-agent --env QUARKUS_TEST_INTEGRATION_TEST_PROFILE=test-with-native-agent --env QUARKUS_NATIVE_CONTAINER_BUILD=true quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 -agentlib:native-image-agent=access-filter-file=quarkus-access-filter.json,caller-filter-file=quarkus-caller-filter.json,config-output-dir=native-image-agent-base-config, -jar quarkus-app/quarkus-run.jar

had to use -v /$(pwd)/target:/project:z and again / in quarkus-app/quarkus-run.jar

galderz commented 5 months ago

Also, the reflection configuration that you see looks right. There are some additional registrations that originate in the JDK but we can't really filter those without affecting other use cases down the line.

ok. so all the exported and filtered files are in native-image-agent-base-config

Actually, native-image-agent-base-config is what comes out of the native image agent. We then do some additional filtering on resources configuration and we put the final versions inside native-image-agent-final-config folder.

Thanks for helping working out the docker command for windows. I did suspect the \ in the jar path would be an issue, but wanted to have access to a machine to verify. I'm a little surprised about the volume path changes since we transform the volume paths into paths with / for other docker+windows use cases. Anyway, I've just got hold of a machine and I'll be testing changes in the next few days.

vsevel commented 5 months ago

We then do some additional filtering on resources configuration and we put the final versions inside native-image-agent-final-config folder.

I do not have the final folder:

[sevel@graalvm galderapp]$ ls -al target/
total 24
drwxrws---. 13 sevel cpy 4096 Apr 26 12:28 .
drwxrws---.  5 sevel cpy  142 Apr 26 12:27 ..
drwxrws---.  3 sevel cpy   47 Apr 26 12:27 classes
drwxrws---.  2 sevel cpy  117 Apr 26 12:28 failsafe-reports
-rw-rw----.  1 sevel cpy 2893 Apr 26 12:27 galderapp-1.0.0-SNAPSHOT.jar
drwxrws---.  3 sevel cpy   25 Apr 26 12:27 generated-sources
drwxrws---.  3 sevel cpy   30 Apr 26 12:27 generated-test-sources
drwxrws---.  2 sevel cpy   28 Apr 26 12:27 maven-archiver
drwxrws---.  3 sevel cpy   35 Apr 26 12:27 maven-status
drwxr-sr-x.  3 sevel cpy  222 Apr 26 12:28 native-image-agent-base-config
drwxrws---.  3 sevel cpy   23 Apr 26 12:27 quarkus
-rw-rw----.  1 sevel cpy  258 Apr 26 12:27 quarkus-access-filter.json
drwxrwsr--.  5 sevel cpy  102 Apr 26 12:27 quarkus-app
-rw-rw----.  1 sevel cpy  197 Apr 26 12:27 quarkus-artifact.properties
-rw-rw----.  1 sevel cpy  258 Apr 26 12:27 quarkus-caller-filter.json
-rw-rw----.  1 sevel cpy  635 Apr 26 12:28 quarkus.log
drwxrws---.  2 sevel cpy   93 Apr 26 12:27 surefire-reports
drwxrws---.  3 sevel cpy   47 Apr 26 12:28 test-classes
galderz commented 5 months ago

Ah yes, you need to make sure you add the <goal>native-image-agent</goal> to your quarkus-maven-plugin definition in order to do the post-processing of resource configuration. E.g.

<plugin>
    <groupId>${quarkus.platform.group-id}</groupId>
    <artifactId>quarkus-maven-plugin</artifactId>
    <version>${quarkus.platform.version}</version>
    <extensions>true</extensions>
    <executions>
        <execution>
            <goals>
                <goal>build</goal>
                <goal>generate-code</goal>
                <goal>generate-code-tests</goal>
                <goal>native-image-agent</goal>
            </goals>
        </execution>
    </executions>
</plugin>
vsevel commented 5 months ago

that works better. predefined-classes-config.json is gone entries filtered in ressource-config nothing in the others (which is a bit unfortunate for reflection because that is a common case, and there is a bit of noise there)

vsevel commented 5 months ago

by the way, I talked to a couple of people doing the eap migrations about this feature. and they definitely see the value. this is going to be very well received by the teams I think.

galderz commented 5 months ago

@vsevel I pushed a fix to the branch to change the jar path and that was enough to make the test I had added pass. The volume path structure is fine so need to fiddle with that. Let us know when you're happy and we can get this integrated. I will create a follow up task to enable extensions to register pakages that can be filtered (e.g. caffeine, kafka...etc).

vsevel commented 5 months ago

I just tested and everything works perfectly indeed @galderz, both in windows and git bash shell. looks good!