Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Compile to native binaries using GraalVM Native Image #36

Open
muscovitebob opened this issue May 11, 2020 · 4 comments
Open

Compile to native binaries using GraalVM Native Image #36

muscovitebob opened this issue May 11, 2020 · 4 comments

Comments

@muscovitebob
Copy link

It should be possible to get rid of the JVM overhead using the GraalVM Native Image mechanism. I will take a look at this later, but have you folks already experimented with this route before?

@muscovitebob
Copy link
Author

muscovitebob commented May 11, 2020

I got this working with some tinkering, after needing to generate some configuration json for GraalVM to cope with the reflection used in http libs. Writeup coming soon but here's the results for the pure startup time:

MBPW:kafka-connect-tools bshilov$ time bin/connect-cli ps
java.lang.Exception:  Error: the Kafka Connect API returned status code 401
	at com.datamountaineer.connect.tools.RestKafkaConnectApi.non2xxException(RestKafkaConnectApi.scala:124)
	at com.datamountaineer.connect.tools.RestKafkaConnectApi.com$datamountaineer$connect$tools$RestKafkaConnectApi$$req(RestKafkaConnectApi.scala:141)
	at com.datamountaineer.connect.tools.RestKafkaConnectApi$$anonfun$activeConnectorNames$1.apply(RestKafkaConnectApi.scala:153)
	at com.datamountaineer.connect.tools.RestKafkaConnectApi$$anonfun$activeConnectorNames$1.apply(RestKafkaConnectApi.scala:152)
	at scala.util.Try$.apply(Try.scala:192)
	at com.datamountaineer.connect.tools.RestKafkaConnectApi.activeConnectorNames(RestKafkaConnectApi.scala:152)
	at com.datamountaineer.connect.tools.ExecuteCommand$.apply(Cli.scala:66)
	at com.datamountaineer.connect.tools.Cli$.main(Cli.scala:209)
	at com.datamountaineer.connect.tools.Cli.main(Cli.scala)


real	0m0.748s
user	0m1.010s
sys	0m0.090s
MBPW:kafka-connect-tools bshilov$ time ./kafka-connect-cli-02 ps
java.lang.Exception:  Error: the Kafka Connect API returned status code 401
	at com.datamountaineer.connect.tools.RestKafkaConnectApi.non2xxException(RestKafkaConnectApi.scala:124)
	at com.datamountaineer.connect.tools.RestKafkaConnectApi.com$datamountaineer$connect$tools$RestKafkaConnectApi$$req(RestKafkaConnectApi.scala:141)
	at com.datamountaineer.connect.tools.RestKafkaConnectApi$$anonfun$activeConnectorNames$1.apply(RestKafkaConnectApi.scala:153)
	at com.datamountaineer.connect.tools.RestKafkaConnectApi$$anonfun$activeConnectorNames$1.apply(RestKafkaConnectApi.scala:152)
	at scala.util.Try$.apply(Try.scala:192)
	at com.datamountaineer.connect.tools.RestKafkaConnectApi.activeConnectorNames(RestKafkaConnectApi.scala:152)
	at com.datamountaineer.connect.tools.ExecuteCommand$.apply(Cli.scala:66)
	at com.datamountaineer.connect.tools.Cli$.main(Cli.scala:209)
	at com.datamountaineer.connect.tools.Cli.main(Cli.scala)


real	0m0.187s
user	0m0.025s
sys	0m0.022s

First is the master jar, second is the GraalVM binary.

This is purely via the executables, no Gradle yet.

@muscovitebob
Copy link
Author

Right so to get to this result you need to install GraalVM and add it to your path:

brew cask install graalvm/tap/graalvm-ce-java8
export PATH=/Library/Java/JavaVirtualMachines/graalvm-ce-java8-20.0.0/Contents/Home/bin:"$PATH"

And then install the Native Image plugin via:

gu install native-image

The next important step is to make sure you have the following variables set to JDK 1.8 Home and Scala 2.11 Home, else the GraalVM image produced will fail with very odd errors. I manage everything via homebrew so the below are my own homebrew installation paths, replace as necessary:

export JAVA_HOME='/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home'
export SCALA_HOME='/usr/local/Cellar/[email protected]/2.11.12'

And for convenience define the home of GraalVM as a var too:

export GRAAL_HOME='/Library/Java/JavaVirtualMachines/graalvm-ce-java8-20.0.0/Contents/Home'

Now we need to generate some files to deal with the usage of reflection in our dependencies. This is done via:

mkdir -p META-INF/native-image
$GRAAL_HOME/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image -jar bin/connect-cli ps

This will actually run the fat jar the current master creates and profile which libraries use reflection and write the results to META-INF/native-image. We pass them to the next step, the actual native image builder:

native-image -H:ReflectionConfigurationFiles=META-INF/native-image/reflect-config.json -H:JNIConfigurationFiles=META-INF/native-image/jni-config.json -H:ResourceConfigurationFiles=META-INF/native-image/resource-config.json -H:EnableURLProtocols=https -jar build/libs/kafka-connect-cli-1.0.8-all.jar kafka-connect-cli-graal

Now this should generate a perfectly functional kafka-connect-cli-graal executable.

@Antwnis
Copy link
Contributor

Antwnis commented May 20, 2020

Hi @muscovitebob - out of curiosity what is the end result ?

i.e. "an executable that is x KBytes rather than y"

Do you get executables for different linux / mac / win etc ? Share a bit insight as i haven't looked into Graal for a while - thx

@helpermethod
Copy link

Hi @Antwnis!

You create different, statically linked binaries for each target platform (Mac, Linux, Win). No JVM installation needed.

Instead of manually creating the reflect-config.json file, I'd suggest using the tracing agent, which can do this automatically (by tracking class-loading / reflection usage at runtime, e.g. during test runs).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants