Native Java with GraalVM

Sylvain Wallez
Sylvain WallezSoftware engineer at Elastic
GraalVM
One compiler to rule them all
Agenda
GraalVM? What’s that?
Building native apps in Java (or other JVM languages)
Case study: a CLI app
Reflections on reflection
Building a Docker image from scratch
Conclusion
Who’s this guy?
Sylvain Wallez - @bluxte
Software Engineer, Cloud team at Elastic
Member of the Apache Software Foundation
Toulouse JUG co-founder
- we’re hiring!
Started using Java in 1997!
GraalVM: an ecosystem
The traditional JVM
Java HotSpot VM
Bytecode interpreter C1 compiler C2 compiler
GC std lib
GraalVM: a new compiler
Java HotSpot VM
JVM Compiler Interface (JVMCI - JEP 243)
Graal compiler
GC std lib
Truffle framework
Sulong (LLVM)
Replacing the bytecode compiler
Problems with C1 & C2
● Code base is old C++ that is hard to understand
● Evolution stalled. Recent changes: mostly intrinsics provided by Intel
Graal is a bytecode compiler written in Java
● Higher level, cleanly organized
● Better inlining and escape analysis
GraalVM ecosystem
Truffle framework:
● Easily build language interpreters from an AST
● Support for JS, Ruby, Python, R & WebAssembly
Sulong:
● An LLVM bitcode interpreter
● Run any LLVM language on the JVM: C, C++, Rust, etc.
Release schedule
First GA version in May 2019
A release every 3 months
Legal & Pricing
Community Edition
● GPL + Classpath Exception (can safely link with it)
Enterprise edition
● Long term support
● More code optimisations, profiling-guided optimizer
● 25$ (Java SE) + 18$ (GraalVM) per month per processor
SubstrateVM
Look Ma! No JVM!
GraalVM
Java HotSpot VM
JVM Compiler Interface (JVMCI - JEP 243)
Graal compiler
GC std lib
Truffle framework
Sulong (LLVM)
SubstrateVM
Java HotSpot VM
JVM Compiler Interface (JVMCI)
Graal compiler
Truffle framework
Sulong (LLVM)
Substrate VMGCstd lib
Why SubstrateVM?
AOT: ahead of time compilation
Produces native executables
● Instant start: no bytecode compilation
→ Great for CLIs and Cloud functions
● Low memory footprint: no metaspace
→ Great for micro services
Why SubstrateVM?
https://twitter.com/brunoborges/status/1114486749722443776
Why not SubstrateVM?
Dynamic Class Loading / Unloading Not supported
Reflection Supported (Requires Configuration)
Dynamic Proxy Supported (Requires Configuration)
InvokeDynamic and Method Handles Not supported
Finalizers Not supported
Serialization Not supported
References Mostly supported
Security Manager Not supported
JVMTI, JMX, other native VM interfaces Not supported
Profiling-based optimization Use GraalVM EE
Getting started
Install GraalVM
● MacOS: brew cask install graalvm/tap/graalvm-ce-java11
● Docker: docker pull oracle/graalvm-ce:19.3.0-java11
● sdkman: sdk install java 19.3.0.r11-grl
Then install SubstrateVM
● gu install native-image
Use case: zkstat
zkstat
A CLI utility to collect statistics on the data stored in ZooKeeper
● Elastic Cloud has 27 ZooKeeper clusters
● Each contains several million nodes
Features
● Dump list of nodes for ingestion in Elasticsearch
● Aggregate statistics by node pattern (count, size, stdev, versions, etc)
● Uses Picocli to parse CLI arguments
zkstat: main & picocli
public class ZkStatCLIApp {
public static void main(String[] args) throws Exception {
new ZkStatCLIApp().run(args);
}
public void run(String... args) throws Exception {
CommandLine commandLine = new CommandLine(new TopLevelCommand());
commandLine.addSubcommand("logs-stats", new LogStatsCommand());
commandLine.addSubcommand("node-stats", new NodeStatsCommand());
commandLine.addSubcommand("nodetype-stats", new NodeTypeStatsCommand());
commandLine.addSubcommand("nodetype-stats-csv", new NodeTypeCSVStatsCommand());
System.exit(commandLine.execute(args));
}
...
zkstat: main & picocli
@CommandLine.Command(name = "zkstat", mixinStandardHelpOptions = true,
description = "A tool to dump ZooKeeper statistics from ZooKeeper storage folder"
)
class TopLevelCommand implements Callable<Integer> {
public Integer call() throws Exception { return 0; }
}
@CommandLine.Command(name = "logs-stats", description = "Collects transaction log changes into JSON")
class LogStatsCommand implements Callable<Integer> {
@CommandLine.Parameters(index = "0", description = "ZooKeeper datadir")
private String dataDir;
@CommandLine.Parameters(index = "1", description = "Output filename ('-' for stdout)", defaultValue = "-")
private String outputFilename;
@CommandLine.Option(names = {"-t", "--target-index"}, description = "Target index", defaultValue = "zktranlog")
private String targetIndexName;
@Override
public Integer call() throws Exception {
// Do something useful here
}
}
zkstat: picocli in action
$ zkstat logs-stats
Missing required parameters: <dataDir>, <outputFilename>
Usage: zkstat logs-stats [-bc] [-t=<targetIndexName>] <dataDir> <outputFilename>
Collects transaction log changes into JSON
<dataDir> ZooKeeper datadir
<outputFilename> Output filename ('-' for stdout)
-b, --bulk Format JSON as a bulk request to ingest into ElasticSearch
-c, --compress Gzipped output
-t, --target-index=<targetIndexName>
Target index
zkstat: Gradle build (Kotlin edition)
plugins {
id("java")
}
group = "co.elastic.cloud.zookeeper.stats"
version = "1.0.0"
val appName = "zkstat"
val mainClass = "co.elastic.cloud.zookeeper.stats.ZkStatCLIApp"
repositories {
mavenCentral()
}
dependencies {
implementation("org.apache.zookeeper:zookeeper:3.5.3")
implementation("org.slf4j:slf4j-simple:1.7.25")
implementation("info.picocli:picocli:4.0.4")
testImplementation("junit:junit:4.12")
}
From fat jar to native image
tasks.register<Jar>("fatJar") {
archiveBaseName.set("$appName-full")
manifest {
attributes["Main-Class"] = mainClass
}
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
with(tasks.jar.get())
}
$ gradlew fatJar
...
$ ls -lh build/libs/zkstat-full-1.0.0.jar
-rw-r--r-- 1 sylvain staff 2.7M Dec 25 13:37 build/libs/zkstat-full-1.0.0.jar
$ java -jar build/lib/zkstat-full-1.0.0.jar
Usage: zkstat [-hV] [COMMAND]
...
From fat jar to native image
plugins {
id("org.mikeneck.graalvm-native-image") version "0.1.1"
}
nativeImage {
setGraalVmHome(System.getProperty("java.home"))
setMainClass(mainClass)
setExecutableName(appName)
if (System.getProperty("os.name") == "Linux") arguments("--static") // To allow "FROM scratch"
}
$ gradlew nativeImage
...
From fat jar to native image
$ gradlew nativeImage
> Task :nativeImage
Shutdown Server(pid: 30312, port: 49557)
Build on Server(pid: 36613, port: 53328)*
[zkstat:36613] classlist: 2,303.05 ms
....
[zkstat:36613] universe: 928.73 ms
Warning: Reflection method java.lang.Class.forName invoked at picocli.CommandLine$BuiltIn$ClassConverter.convert(Co
Warning: Reflection method java.lang.Class.newInstance invoked at picocli.CommandLine$DefaultFactory.create(Command
Warning: Reflection method java.lang.Class.getMethods invoked at picocli.CommandLine.getCommandMethods(CommandLine.
Warning: Reflection method java.lang.Class.getDeclaredMethods invoked at picocli.CommandLine.getCommandMethods(Comm
Warning: Reflection method java.lang.Class.getDeclaredMethods invoked at picocli.CommandLine$Model$CommandReflectio
Warning: Reflection method java.lang.Class.getDeclaredConstructor invoked at picocli.CommandLine$DefaultFactory.cre
Warning: Reflection method java.lang.Class.getDeclaredConstructor invoked at picocli.CommandLine$DefaultFactory.cre
Warning: Reflection method java.lang.Class.getDeclaredFields invoked at picocli.CommandLine$Model$CommandReflection
Warning: Aborting stand-alone image build due to reflection use without configuration.
Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Build on Server(pid: 36613, port: 53328)
[zkstat:36613] classlist: 210.49 ms
...
[zkstat:36613] [total]: 22,315.84 ms
Warning: Image 'zkstat' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallba
Let’s try with this --no-fallback parameter
From fat jar to native image
$ build/native-image/zkstat logs-stats src dest
Exception in thread "main" picocli.CommandLine$InitializationException:
picocli.CommandLine$AutoHelpMixin is not a command: it has no @Command, @Option, @Parameters or
@Unmatched annotations
at picocli.CommandLine$Model$CommandReflection.validateCommandSpec(CommandLine.java:9731)
at picocli.CommandLine$Model$CommandReflection.extractCommandSpec(CommandLine.java:9566)
at picocli.CommandLine$Model$CommandSpec.forAnnotatedObject(CommandLine.java:5116)
at picocli.CommandLine$Model$CommandSpec.mixinStandardHelpOptions(CommandLine.java:5858)
at picocli.CommandLine$Model$CommandReflection.extractCommandSpec(CommandLine.java:9549)
at picocli.CommandLine$Model$CommandSpec.forAnnotatedObject(CommandLine.java:5116)
at picocli.CommandLine.<init>(CommandLine.java:223)
at picocli.CommandLine.<init>(CommandLine.java:196)
at co.elastic.cloud.zookeeper.stats.ZkStatCLIApp.run(ZkStatCLIApp.java:44)
at co.elastic.cloud.zookeeper.stats.ZkStatCLIApp.main(ZkStatCLIApp.java:40)
Configuring reflection
We have to tell native-image:
● what classes are used with reflection
● what classes are proxied
● what resources are loaded from the classpath
● what libraries are load with JNI
Some good builtin heuristics, but cannot guess everything
→ use the tracing agent to create the configs!
$ java -agentlib:native-image-agent=config-output-dir=./graal-config -jar
build/libs/zkstat-full-1.0.0.jar
...
$ ls graal-config
jni-config.json proxy-config.json reflect-config.json resource-config.json
Configuring reflection
reflect-config.json
[
{
"name":"co.elastic.cloud.zookeeper.stats.LogStatsCommand",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allPublicMethods":true
},
{
"name":"co.elastic.cloud.zookeeper.stats.NodeStatsCommand",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allPublicMethods":true
},
{
"name":"co.elastic.cloud.zookeeper.stats.NodeTypeCSVStatsCommand",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allPublicMethods":true
},
{
"name":"co.elastic.cloud.zookeeper.stats.NodeTypeStatsCommand",
"allDeclaredFields":true,
"allDeclaredMethods":true,
Configuring reflection
Picocli comes with an annotation processor that does the job for us!
dependencies {
...
implementation("info.picocli:picocli:4.0.4")
annotationProcessor("info.picocli:picocli-codegen:4.0.4")
}
$ gradlew build
...
$ ls build/classes/java/main/META-INF/native-image/picocli-generated
proxy-config.json reflect-config.json resource-config.json
Configs in META-INF are automatically used by native-image!
Configuring reflection
reflect-config.json
[
{
"name" : "co.elastic.cloud.zookeeper.stats.LogStatsCommand",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true,
"fields" : [
{ "name" : "bulk" },
{ "name" : "compress" },
{ "name" : "dataDir" },
{ "name" : "outputFilename" },
{ "name" : "targetIndexName" }
]
},
{
"name" : "co.elastic.cloud.zookeeper.stats.NodeStatsCommand",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true,
"fields" : [
{ "name" : "compress" },
From fat jar to native image
$ gradlew nativeImage
> Task :nativeImage
Build on Server(pid: 36613, port: 53328)
[zkstat:36613] classlist: 1,004.58 ms
...
[zkstat:36613] write: 370.40 ms
[zkstat:36613] [total]: 27,443.07 ms
BUILD SUCCESSFUL in 32s
4 actionable tasks: 2 executed, 2 up-to-date
$ build/native-image/zkstat
Usage: zkstat [-hV] [COMMAND]
...
$ ls -lh build/native-image/zkstat
-rwxr-xr-x 1 sylvain staff 13M Dec 25 13:37 build/native-image/zkstat*
a yummy standalone executable!
Fat jar edition
Startup time - essential for a CLI!
$ time java -jar build/libs/zkstat-full-1.0.0.jar
Usage: zkstat [-hV] [COMMAND]
A tool to dump ZooKeeper statistics from ZooKeeper storage folder
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Commands:
logs-stats Collects transaction logs changes into JSON
node-stats Collects paths and stats objects from a ZK data dir as CSV
nodetype-stats Computes and outputs as CSV statistics by path type from
a ZK data dir
nodetype-stats-csv Computes and outputs as CSV statistics by path type from
CSV
real 0m0.339s
user 0m0.617s
sys 0m0.080s
RSS 48032 kB
Native image edition
Startup time - essential for a CLI!
$ time build/native-image/zkstat
Usage: zkstat [-hV] [COMMAND]
A tool to dump ZooKeeper statistics from ZooKeeper storage folder
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Commands:
logs-stats Collects transaction logs changes into JSON
node-stats Collects paths and stats objects from a ZK data dir as CSV
nodetype-stats Computes and outputs as CSV statistics by path type from
a ZK data dir
nodetype-stats-csv Computes and outputs as CSV statistics by path type from
CSV
real 0m0.013s
user 0m0.006s
sys 0m0.004s
RSS 5664 kB
real 0m0.339s
user 0m0.617s
sys 0m0.080s
RSS 48032 kB
Docker build
FROM oracle/graalvm-ce:19.3.0-java11 as build
RUN gu install native-image
WORKDIR /project
# Download and cache Gradle
COPY gradlew .
COPY gradle ./gradle
RUN ./gradlew
# Download dependencies and cache them separately from the main source code
COPY build.gradle.kts .
RUN ./gradlew downloadDependencies
# Compile and build native image
COPY src ./src
RUN ./gradlew nativeImage
#------------------------------------------------------------------------------
FROM scratch
COPY --from=build /project/build/native-image/zkstat .
CMD ["/zkstat"]
tasks.register("downloadDependencies") {
println("Downloading dependencies")
configurations.testRuntimeClasspath.get().files
}
Docker build
$ docker build -t zkstat .
Sending build context to Docker daemon 140.8kB
Step 1/13 : FROM oracle/graalvm-ce:19.3.0-java11 as build
---> bc6f2b723104
Step 2/13 : RUN gu install native-image
---> Using cache
---> a296590b05e6
...
Step 13/13 : CMD ["/zkstat"]
---> Running in 7109549122e8
Removing intermediate container 7109549122e8
---> 3894ce2f74ad
Successfully built 3894ce2f74ad
Successfully tagged zkstat:latest
$ docker image ls zkstat
REPOSITORY TAG IMAGE ID CREATED SIZE
zkstat latest 6547de4b0068 3 hours ago 14.2MB
Conclusion
Native-image pros
● Small executable
● Small RAM footprint
● Instant start time
Native-image cons
● May require some setup (reflection)
● We lose a lot of management features
● May not provide optimal performance - but is your code optimal?
Thanks!
Questions?
Sylvain Wallez - @bluxte
1 of 38

More Related Content

What's hot(20)

Graal and Truffle: One VM to Rule Them AllGraal and Truffle: One VM to Rule Them All
Graal and Truffle: One VM to Rule Them All
Thomas Wuerthinger15.3K views
Introduction to GraalVMIntroduction to GraalVM
Introduction to GraalVM
SHASHI KUMAR349 views
Migrating to Java 11Migrating to Java 11
Migrating to Java 11
Arto Santala2.1K views
From Java 11 to 17 and beyond.pdfFrom Java 11 to 17 and beyond.pdf
From Java 11 to 17 and beyond.pdf
José Paumard472 views
Quarkus Denmark 2019Quarkus Denmark 2019
Quarkus Denmark 2019
Max Andersen606 views
Gitlab CI/CDGitlab CI/CD
Gitlab CI/CD
JEMLI Fathi1.1K views
Spring Native and Spring AOTSpring Native and Spring AOT
Spring Native and Spring AOT
VMware Tanzu1.6K views
Linux Profiling at NetflixLinux Profiling at Netflix
Linux Profiling at Netflix
Brendan Gregg1M views
Using GitLab CIUsing GitLab CI
Using GitLab CI
ColCh9.9K views
Gitlab ci-cdGitlab ci-cd
Gitlab ci-cd
Dan MAGIER3.4K views
GraalVm and QuarkusGraalVm and Quarkus
GraalVm and Quarkus
Sascha Rodekamp509 views
Quarkus   k8sQuarkus   k8s
Quarkus k8s
Georgios Andrianakis728 views
Java Micro-BenchmarkingJava Micro-Benchmarking
Java Micro-Benchmarking
Constantine Nosovsky10.3K views
Helm 3Helm 3
Helm 3
Matthew Farina1.3K views
Introduction to GitHub ActionsIntroduction to GitHub Actions
Introduction to GitHub Actions
Knoldus Inc.3.2K views
Java11 New FeaturesJava11 New Features
Java11 New Features
Haim Michael1.3K views

Similar to Native Java with GraalVM(20)

Ob1k presentation at Java.ILOb1k presentation at Java.IL
Ob1k presentation at Java.IL
Eran Harel1.6K views
Silicon Valley JUG: JVM MechanicsSilicon Valley JUG: JVM Mechanics
Silicon Valley JUG: JVM Mechanics
Azul Systems, Inc.543 views
Adopting GraalVM - NE Scala 2019Adopting GraalVM - NE Scala 2019
Adopting GraalVM - NE Scala 2019
Petr Zapletal422 views
Mastering Java ByteCodeMastering Java ByteCode
Mastering Java ByteCode
Ecommerce Solution Provider SysIQ1.7K views
Curator introCurator intro
Curator intro
Jordan Zimmerman13.7K views
Cli jbugCli jbug
Cli jbug
maeste722 views
AS7 and CLIAS7 and CLI
AS7 and CLI
JBug Italy3.2K views
Spring BootSpring Boot
Spring Boot
Jiayun Zhou2.5K views
Jvm Performance TunningJvm Performance Tunning
Jvm Performance Tunning
Terry Cho2.9K views
Jvm Performance TunningJvm Performance Tunning
Jvm Performance Tunning
guest1f2740941 views
Integration tests: use the containers, Luke!Integration tests: use the containers, Luke!
Integration tests: use the containers, Luke!
Roberto Franchini1.9K views
Nanocloud   cloud scale jvmNanocloud   cloud scale jvm
Nanocloud cloud scale jvm
aragozin1K views
TypeScript for Java DevelopersTypeScript for Java Developers
TypeScript for Java Developers
Yakov Fain3.9K views

More from Sylvain Wallez(12)

Recently uploaded(20)

Best Mics For Your Live StreamingBest Mics For Your Live Streaming
Best Mics For Your Live Streaming
ontheflystream6 views
Neo4j y GenAI Neo4j y GenAI
Neo4j y GenAI
Neo4j27 views
Winter '24 Release Chat.pdfWinter '24 Release Chat.pdf
Winter '24 Release Chat.pdf
melbourneauuser9 views
HarshithAkkapelli_Presentation.pdfHarshithAkkapelli_Presentation.pdf
HarshithAkkapelli_Presentation.pdf
harshithakkapelli10 views

Native Java with GraalVM

  • 1. GraalVM One compiler to rule them all
  • 2. Agenda GraalVM? What’s that? Building native apps in Java (or other JVM languages) Case study: a CLI app Reflections on reflection Building a Docker image from scratch Conclusion
  • 3. Who’s this guy? Sylvain Wallez - @bluxte Software Engineer, Cloud team at Elastic Member of the Apache Software Foundation Toulouse JUG co-founder - we’re hiring! Started using Java in 1997!
  • 5. The traditional JVM Java HotSpot VM Bytecode interpreter C1 compiler C2 compiler GC std lib
  • 6. GraalVM: a new compiler Java HotSpot VM JVM Compiler Interface (JVMCI - JEP 243) Graal compiler GC std lib Truffle framework Sulong (LLVM)
  • 7. Replacing the bytecode compiler Problems with C1 & C2 ● Code base is old C++ that is hard to understand ● Evolution stalled. Recent changes: mostly intrinsics provided by Intel Graal is a bytecode compiler written in Java ● Higher level, cleanly organized ● Better inlining and escape analysis
  • 8. GraalVM ecosystem Truffle framework: ● Easily build language interpreters from an AST ● Support for JS, Ruby, Python, R & WebAssembly Sulong: ● An LLVM bitcode interpreter ● Run any LLVM language on the JVM: C, C++, Rust, etc.
  • 9. Release schedule First GA version in May 2019 A release every 3 months
  • 10. Legal & Pricing Community Edition ● GPL + Classpath Exception (can safely link with it) Enterprise edition ● Long term support ● More code optimisations, profiling-guided optimizer ● 25$ (Java SE) + 18$ (GraalVM) per month per processor
  • 12. GraalVM Java HotSpot VM JVM Compiler Interface (JVMCI - JEP 243) Graal compiler GC std lib Truffle framework Sulong (LLVM)
  • 13. SubstrateVM Java HotSpot VM JVM Compiler Interface (JVMCI) Graal compiler Truffle framework Sulong (LLVM) Substrate VMGCstd lib
  • 14. Why SubstrateVM? AOT: ahead of time compilation Produces native executables ● Instant start: no bytecode compilation → Great for CLIs and Cloud functions ● Low memory footprint: no metaspace → Great for micro services
  • 16. Why not SubstrateVM? Dynamic Class Loading / Unloading Not supported Reflection Supported (Requires Configuration) Dynamic Proxy Supported (Requires Configuration) InvokeDynamic and Method Handles Not supported Finalizers Not supported Serialization Not supported References Mostly supported Security Manager Not supported JVMTI, JMX, other native VM interfaces Not supported Profiling-based optimization Use GraalVM EE
  • 17. Getting started Install GraalVM ● MacOS: brew cask install graalvm/tap/graalvm-ce-java11 ● Docker: docker pull oracle/graalvm-ce:19.3.0-java11 ● sdkman: sdk install java 19.3.0.r11-grl Then install SubstrateVM ● gu install native-image
  • 19. zkstat A CLI utility to collect statistics on the data stored in ZooKeeper ● Elastic Cloud has 27 ZooKeeper clusters ● Each contains several million nodes Features ● Dump list of nodes for ingestion in Elasticsearch ● Aggregate statistics by node pattern (count, size, stdev, versions, etc) ● Uses Picocli to parse CLI arguments
  • 20. zkstat: main & picocli public class ZkStatCLIApp { public static void main(String[] args) throws Exception { new ZkStatCLIApp().run(args); } public void run(String... args) throws Exception { CommandLine commandLine = new CommandLine(new TopLevelCommand()); commandLine.addSubcommand("logs-stats", new LogStatsCommand()); commandLine.addSubcommand("node-stats", new NodeStatsCommand()); commandLine.addSubcommand("nodetype-stats", new NodeTypeStatsCommand()); commandLine.addSubcommand("nodetype-stats-csv", new NodeTypeCSVStatsCommand()); System.exit(commandLine.execute(args)); } ...
  • 21. zkstat: main & picocli @CommandLine.Command(name = "zkstat", mixinStandardHelpOptions = true, description = "A tool to dump ZooKeeper statistics from ZooKeeper storage folder" ) class TopLevelCommand implements Callable<Integer> { public Integer call() throws Exception { return 0; } } @CommandLine.Command(name = "logs-stats", description = "Collects transaction log changes into JSON") class LogStatsCommand implements Callable<Integer> { @CommandLine.Parameters(index = "0", description = "ZooKeeper datadir") private String dataDir; @CommandLine.Parameters(index = "1", description = "Output filename ('-' for stdout)", defaultValue = "-") private String outputFilename; @CommandLine.Option(names = {"-t", "--target-index"}, description = "Target index", defaultValue = "zktranlog") private String targetIndexName; @Override public Integer call() throws Exception { // Do something useful here } }
  • 22. zkstat: picocli in action $ zkstat logs-stats Missing required parameters: <dataDir>, <outputFilename> Usage: zkstat logs-stats [-bc] [-t=<targetIndexName>] <dataDir> <outputFilename> Collects transaction log changes into JSON <dataDir> ZooKeeper datadir <outputFilename> Output filename ('-' for stdout) -b, --bulk Format JSON as a bulk request to ingest into ElasticSearch -c, --compress Gzipped output -t, --target-index=<targetIndexName> Target index
  • 23. zkstat: Gradle build (Kotlin edition) plugins { id("java") } group = "co.elastic.cloud.zookeeper.stats" version = "1.0.0" val appName = "zkstat" val mainClass = "co.elastic.cloud.zookeeper.stats.ZkStatCLIApp" repositories { mavenCentral() } dependencies { implementation("org.apache.zookeeper:zookeeper:3.5.3") implementation("org.slf4j:slf4j-simple:1.7.25") implementation("info.picocli:picocli:4.0.4") testImplementation("junit:junit:4.12") }
  • 24. From fat jar to native image tasks.register<Jar>("fatJar") { archiveBaseName.set("$appName-full") manifest { attributes["Main-Class"] = mainClass } from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) with(tasks.jar.get()) } $ gradlew fatJar ... $ ls -lh build/libs/zkstat-full-1.0.0.jar -rw-r--r-- 1 sylvain staff 2.7M Dec 25 13:37 build/libs/zkstat-full-1.0.0.jar $ java -jar build/lib/zkstat-full-1.0.0.jar Usage: zkstat [-hV] [COMMAND] ...
  • 25. From fat jar to native image plugins { id("org.mikeneck.graalvm-native-image") version "0.1.1" } nativeImage { setGraalVmHome(System.getProperty("java.home")) setMainClass(mainClass) setExecutableName(appName) if (System.getProperty("os.name") == "Linux") arguments("--static") // To allow "FROM scratch" } $ gradlew nativeImage ...
  • 26. From fat jar to native image $ gradlew nativeImage > Task :nativeImage Shutdown Server(pid: 30312, port: 49557) Build on Server(pid: 36613, port: 53328)* [zkstat:36613] classlist: 2,303.05 ms .... [zkstat:36613] universe: 928.73 ms Warning: Reflection method java.lang.Class.forName invoked at picocli.CommandLine$BuiltIn$ClassConverter.convert(Co Warning: Reflection method java.lang.Class.newInstance invoked at picocli.CommandLine$DefaultFactory.create(Command Warning: Reflection method java.lang.Class.getMethods invoked at picocli.CommandLine.getCommandMethods(CommandLine. Warning: Reflection method java.lang.Class.getDeclaredMethods invoked at picocli.CommandLine.getCommandMethods(Comm Warning: Reflection method java.lang.Class.getDeclaredMethods invoked at picocli.CommandLine$Model$CommandReflectio Warning: Reflection method java.lang.Class.getDeclaredConstructor invoked at picocli.CommandLine$DefaultFactory.cre Warning: Reflection method java.lang.Class.getDeclaredConstructor invoked at picocli.CommandLine$DefaultFactory.cre Warning: Reflection method java.lang.Class.getDeclaredFields invoked at picocli.CommandLine$Model$CommandReflection Warning: Aborting stand-alone image build due to reflection use without configuration. Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception Build on Server(pid: 36613, port: 53328) [zkstat:36613] classlist: 210.49 ms ... [zkstat:36613] [total]: 22,315.84 ms Warning: Image 'zkstat' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallba
  • 27. Let’s try with this --no-fallback parameter From fat jar to native image $ build/native-image/zkstat logs-stats src dest Exception in thread "main" picocli.CommandLine$InitializationException: picocli.CommandLine$AutoHelpMixin is not a command: it has no @Command, @Option, @Parameters or @Unmatched annotations at picocli.CommandLine$Model$CommandReflection.validateCommandSpec(CommandLine.java:9731) at picocli.CommandLine$Model$CommandReflection.extractCommandSpec(CommandLine.java:9566) at picocli.CommandLine$Model$CommandSpec.forAnnotatedObject(CommandLine.java:5116) at picocli.CommandLine$Model$CommandSpec.mixinStandardHelpOptions(CommandLine.java:5858) at picocli.CommandLine$Model$CommandReflection.extractCommandSpec(CommandLine.java:9549) at picocli.CommandLine$Model$CommandSpec.forAnnotatedObject(CommandLine.java:5116) at picocli.CommandLine.<init>(CommandLine.java:223) at picocli.CommandLine.<init>(CommandLine.java:196) at co.elastic.cloud.zookeeper.stats.ZkStatCLIApp.run(ZkStatCLIApp.java:44) at co.elastic.cloud.zookeeper.stats.ZkStatCLIApp.main(ZkStatCLIApp.java:40)
  • 28. Configuring reflection We have to tell native-image: ● what classes are used with reflection ● what classes are proxied ● what resources are loaded from the classpath ● what libraries are load with JNI Some good builtin heuristics, but cannot guess everything → use the tracing agent to create the configs! $ java -agentlib:native-image-agent=config-output-dir=./graal-config -jar build/libs/zkstat-full-1.0.0.jar ... $ ls graal-config jni-config.json proxy-config.json reflect-config.json resource-config.json
  • 30. Configuring reflection Picocli comes with an annotation processor that does the job for us! dependencies { ... implementation("info.picocli:picocli:4.0.4") annotationProcessor("info.picocli:picocli-codegen:4.0.4") } $ gradlew build ... $ ls build/classes/java/main/META-INF/native-image/picocli-generated proxy-config.json reflect-config.json resource-config.json Configs in META-INF are automatically used by native-image!
  • 31. Configuring reflection reflect-config.json [ { "name" : "co.elastic.cloud.zookeeper.stats.LogStatsCommand", "allDeclaredConstructors" : true, "allPublicConstructors" : true, "allDeclaredMethods" : true, "allPublicMethods" : true, "fields" : [ { "name" : "bulk" }, { "name" : "compress" }, { "name" : "dataDir" }, { "name" : "outputFilename" }, { "name" : "targetIndexName" } ] }, { "name" : "co.elastic.cloud.zookeeper.stats.NodeStatsCommand", "allDeclaredConstructors" : true, "allPublicConstructors" : true, "allDeclaredMethods" : true, "allPublicMethods" : true, "fields" : [ { "name" : "compress" },
  • 32. From fat jar to native image $ gradlew nativeImage > Task :nativeImage Build on Server(pid: 36613, port: 53328) [zkstat:36613] classlist: 1,004.58 ms ... [zkstat:36613] write: 370.40 ms [zkstat:36613] [total]: 27,443.07 ms BUILD SUCCESSFUL in 32s 4 actionable tasks: 2 executed, 2 up-to-date $ build/native-image/zkstat Usage: zkstat [-hV] [COMMAND] ... $ ls -lh build/native-image/zkstat -rwxr-xr-x 1 sylvain staff 13M Dec 25 13:37 build/native-image/zkstat* a yummy standalone executable!
  • 33. Fat jar edition Startup time - essential for a CLI! $ time java -jar build/libs/zkstat-full-1.0.0.jar Usage: zkstat [-hV] [COMMAND] A tool to dump ZooKeeper statistics from ZooKeeper storage folder -h, --help Show this help message and exit. -V, --version Print version information and exit. Commands: logs-stats Collects transaction logs changes into JSON node-stats Collects paths and stats objects from a ZK data dir as CSV nodetype-stats Computes and outputs as CSV statistics by path type from a ZK data dir nodetype-stats-csv Computes and outputs as CSV statistics by path type from CSV real 0m0.339s user 0m0.617s sys 0m0.080s RSS 48032 kB
  • 34. Native image edition Startup time - essential for a CLI! $ time build/native-image/zkstat Usage: zkstat [-hV] [COMMAND] A tool to dump ZooKeeper statistics from ZooKeeper storage folder -h, --help Show this help message and exit. -V, --version Print version information and exit. Commands: logs-stats Collects transaction logs changes into JSON node-stats Collects paths and stats objects from a ZK data dir as CSV nodetype-stats Computes and outputs as CSV statistics by path type from a ZK data dir nodetype-stats-csv Computes and outputs as CSV statistics by path type from CSV real 0m0.013s user 0m0.006s sys 0m0.004s RSS 5664 kB real 0m0.339s user 0m0.617s sys 0m0.080s RSS 48032 kB
  • 35. Docker build FROM oracle/graalvm-ce:19.3.0-java11 as build RUN gu install native-image WORKDIR /project # Download and cache Gradle COPY gradlew . COPY gradle ./gradle RUN ./gradlew # Download dependencies and cache them separately from the main source code COPY build.gradle.kts . RUN ./gradlew downloadDependencies # Compile and build native image COPY src ./src RUN ./gradlew nativeImage #------------------------------------------------------------------------------ FROM scratch COPY --from=build /project/build/native-image/zkstat . CMD ["/zkstat"] tasks.register("downloadDependencies") { println("Downloading dependencies") configurations.testRuntimeClasspath.get().files }
  • 36. Docker build $ docker build -t zkstat . Sending build context to Docker daemon 140.8kB Step 1/13 : FROM oracle/graalvm-ce:19.3.0-java11 as build ---> bc6f2b723104 Step 2/13 : RUN gu install native-image ---> Using cache ---> a296590b05e6 ... Step 13/13 : CMD ["/zkstat"] ---> Running in 7109549122e8 Removing intermediate container 7109549122e8 ---> 3894ce2f74ad Successfully built 3894ce2f74ad Successfully tagged zkstat:latest $ docker image ls zkstat REPOSITORY TAG IMAGE ID CREATED SIZE zkstat latest 6547de4b0068 3 hours ago 14.2MB
  • 37. Conclusion Native-image pros ● Small executable ● Small RAM footprint ● Instant start time Native-image cons ● May require some setup (reflection) ● We lose a lot of management features ● May not provide optimal performance - but is your code optimal?